← 学习库 Think Python (2e) · 中英对照 目录

Chapter 7  Iteration 第 7 章 迭代

本页译自 Think Python 2e(Allen B. Downey)· Chapter 7 Iteration。代码块保留英文原文不翻译;正文段段对照,中文块可用右下角按钮隐藏。

7.1 Multiple assignment 7.1 多重赋值

As you may have discovered, it is legal to make more than one assignment to the same variable. A new assignment makes an existing variable refer to a new value (and stop referring to the old value).

你可能已经发现,对同一个变量进行多次赋值是合法的。新的赋值会让已有的变量指向一个新值(并停止指向旧值)。
bruce = 5
print bruce,
bruce = 7
print bruce

The output of this program is 5 7, because the first time 5 700 is printed, its value is 5, and the second time, its value is 7. The comma at the end of the first 5 700 statement suppresses the newline, which is why both outputs appear on the same line.

这段程序的输出是 5 7,因为第一次打印 5 700 时它的值是 5,第二次打印时它的值是 7。第一条 5 700 语句末尾的逗号抑制了换行,所以两次输出显示在同一行。

Figure 7.1 shows what multiple assignment looks like in a state diagram.

图 7.1 展示了多重赋值在状态图中的样子。

Figure 7.1: State diagram.

图 7.1:状态图(原书插图未收录)

With multiple assignment it is especially important to distinguish between an assignment operation and a statement of equality. Because Python uses the equal sign (=) for assignment, it is tempting to interpret a statement like 5 700 as a statement of equality. It is not!

使用多重赋值时,区分「赋值操作」和「相等陈述」尤为关键。因为 Python 用等号(=)表示赋值,我们很容易把诸如 5 700 这样的语句当成相等陈述。其实不然!

First, equality is a symmetric relation and assignment is not. For example, in mathematics, if a=7 then 7=a. But in Python, the statement 5 700 is legal and 5 700 is not.

首先,相等是一种对称关系,而赋值不是。例如在数学中,若 a=7,则 7=a。但在 Python 中,5 700 是合法的,而 5 700 不合法。

Furthermore, in mathematics, a statement of equality is either true or false, for all time. If a=b now, then a will always equal b. In Python, an assignment statement can make two variables equal, but they don’t have to stay that way:

再者,在数学中,相等陈述永远为真或为假。如果现在 a=b,那么 a 永远等于 b。而在 Python 中,赋值语句可以让两个变量相等,但它们未必一直相等:
5 7000019

The third line changes the value of = but does not change the value of =, so they are no longer equal.

第三行改变了 = 的值,却没有改变 = 的值,于是它们不再相等。

Although multiple assignment is frequently helpful, you should use it with caution. If the values of variables change frequently, it can make the code difficult to read and debug.

尽管多重赋值常常很有用,你仍需谨慎使用。如果变量的值频繁变化,代码会变得难以阅读与调试。

7.2 Updating variables 7.2 变量更新

One of the most common forms of multiple assignment is an update, where the new value of the variable depends on the old.

多重赋值最常见的形式之一是更新,即变量的新值依赖于旧值。
5 7000024

This means “get the current value of =, add one, and then update = with the new value.”

意思是「取出 = 的当前值,加一,然后用新值更新 =」。

If you try to update a variable that doesn’t exist, you get an error, because Python evaluates the right side before it assigns a value to =:

如果你尝试更新一个不存在的变量,会得到一个错误,因为 Python 在给 = 赋值之前先计算右侧。
5 7000031

Before you can update a variable, you have to initialize it, usually with a simple assignment:

在更新变量之前,必须先对它做初始化,通常用一个简单的赋值:
5 7000032

Updating a variable by adding 1 is called an increment; subtracting 1 is called a decrement.

给变量加 1 的更新称为递增;减 1 称为递减

7.3 The 5 700 statement 7.3 5 700 语句

Computers are often used to automate repetitive tasks. Repeating identical or similar tasks without making errors is something that computers do well and people do poorly.

计算机常被用来自动完成重复性任务。无差错地重复相同或相似的任务,是计算机擅长而人类不擅长的。

We have seen two programs, 5 7000035 and 5 70000, that use recursion to perform repetition, which is also called iteration. Because iteration is so common, Python provides several language features to make it easier. One is the 5 7 statement we saw in Section 4.2. We’ll get back to that later.

我们见过两个使用递归来实现重复的程序,5 7000038 和 5 70000,这种重复也叫迭代。因为迭代如此常见,Python 提供了几种语言特性来简化它。其一是我们在 4.2 节见过的 5 7 语句。这个稍后再讲。

Another is the 5 700 statement. Here is a version of 5 7000042 that uses a 5 700 statement:

另一个就是 5 700 语句。下面是一个用 5 700 语句实现的 5 7000046 版本:
5 7000047

You can almost read the 5 700 statement as if it were English. It means, “While = is greater than 0, display the value of = and then reduce the value of = by 1. When you get to 0, display the word 5 7000052”

你几乎可以像读英语一样读 5 700 语句。它的意思是:「只要 = 大于 0,就显示 = 的值,然后把 = 的值减 1。当 = 减到 0 时,显示 5 7000058」

More formally, here is the flow of execution for a 5 700 statement:

更正式地说,5 700 语句的执行流程如下:
  1. Evaluate the condition, yielding 5 70 or 5 700.
  2. If the condition is false, exit the 5 700 statement and continue execution at the next statement.
  3. If the condition is true, execute the body and then go back to step 1.
  1. 求值条件,得到 5 70 或 5 700。
  2. 如果条件为假,退出 5 700 语句,从下一条语句继续执行。
  3. 如果条件为真,执行循环体,然后回到第 1 步。

This type of flow is called a loop because the third step loops back around to the top.

这种流程称为循环,因为第三步又绕回到顶部。

The body of the loop should change the value of one or more variables so that eventually the condition becomes false and the loop terminates. Otherwise the loop will repeat forever, which is called an infinite loop. An endless source of amusement for computer scientists is the observation that the directions on shampoo, “Lather, rinse, repeat,” are an infinite loop.

循环体应当改变一个或多个变量的值,使条件最终变为假、循环得以终止。否则循环会永远重复下去,这称为无限循环。计算机科学家们百听不厌的一个笑料是:洗发水瓶上的说明「起泡、冲洗、重复」就是一个无限循环。

In the case of 5 7000067, we can prove that the loop terminates because we know that the value of = is finite, and we can see that the value of = gets smaller each time through the loop, so eventually we have to get to 0. In other cases, it is not so easy to tell:

5 7000070 为例,我们可以证明循环会终止,因为我们知道 = 的值是有限的,而且每经过一次循环 = 都会变小,所以最终必然减到 0。其他情形下就没这么容易判断了:
5 7000073

The condition for this loop is 5 7000, so the loop will continue until = is =, which makes the condition false.

这个循环的条件是 5 7000,所以循环会一直进行,直到 = 等于 =,此时条件为假。

Each time through the loop, the program outputs the value of = and then checks whether it is even or odd. If it is even, = is divided by 2. If it is odd, the value of = is replaced with 5 700. For example, if the argument passed to 5 700008 is 3, the resulting sequence is 3, 10, 5, 16, 8, 4, 2, 1.

每经过一次循环,程序输出 = 的值,然后判断它是偶数还是奇数。如果是偶数,则 = 除以 2;如果是奇数,则把 = 的值替换为 5 700。例如,传给 5 700008 的实参是 3 时,得到的序列是 3、10、5、16、8、4、2、1。

Since = sometimes increases and sometimes decreases, there is no obvious proof that = will ever reach 1, or that the program terminates. For some particular values of =, we can prove termination. For example, if the starting value is a power of two, then the value of = will be even each time through the loop until it reaches 1. The previous example ends with such a sequence, starting with 16.

由于 = 有时增大有时减小,并没有明显的证据表明 = 一定会到达 1,也无法证明程序一定会终止。对某些特定的 = 值,我们可以证明它会终止。例如,如果起始值是 2 的幂,那么每次循环时 = 都是偶数,直到减为 1。前面那个例子就终于这样一段从 16 开始的序列。

The hard question is whether we can prove that this program terminates for all positive values of =. So far, no one has been able to prove it or disprove it! (See 5 7000099.)

真正困难的问题是:能否证明这个程序对所有正数 = 都会终止。到目前为止,还没有人能证明它证伪它!(参见 5 7000101。)

Exercise 1

习题 1

Rewrite the function 5 70001 from Section 5.8 using iteration instead of recursion.

用迭代而非递归,改写 5.8 节的 5 70001 函数。

7.4 5 700 7.4 5 700

Sometimes you don’t know it’s time to end a loop until you get half way through the body. In that case you can use the 5 700 statement to jump out of the loop.

有时你直到执行到循环体一半,才知道该结束循环了。这时可以用 5 700 语句跳出循环。

For example, suppose you want to take input from the user until they type 5 70. You could write:

例如,假设你想不断接收用户输入,直到他们输入 5 70 为止。可以写成:
5 7000110

The loop condition is 5 70, which is always true, so the loop runs until it hits the break statement.

循环条件为 5 70,永远是真,所以循环会一直运行,直到遇到 break 语句。

Each time through, it prompts the user with an angle bracket. If the user types 5 70, the 5 700 statement exits the loop. Otherwise the program echoes whatever the user types and goes back to the top of the loop. Here’s a sample run:

每经过一次循环,它都会用一个尖括号提示用户。如果用户输入 5 70,5 700 语句就退出循环。否则程序回显用户输入的内容,并回到循环顶部。下面是一次示例运行:
5 7000117

This way of writing 5 700 loops is common because you can check the condition anywhere in the loop (not just at the top) and you can express the stop condition affirmatively (“stop when this happens”) rather than negatively (“keep going until that happens.”)

这种写 5 700 循环的方式很常见,因为你可以在循环中的任意位置(不只是在顶部)检查条件,而且可以把停止条件表达为肯定形式(「发生这件事就停」),而不是否定形式(「在那件事发生之前一直继续」)。

7.5 Square roots 7.5 平方根

Loops are often used in programs that compute numerical results by starting with an approximate answer and iteratively improving it.

循环常用于这样的程序:从一个近似答案出发,通过迭代不断改进它,从而计算出数值结果。

For example, one way of computing square roots is Newton’s method. Suppose that you want to know the square root of a. If you start with almost any estimate, x, you can compute a better estimate with the following formula:

例如,计算平方根的一种方法是牛顿法。假设你想求 a 的平方根。只要从一个任意的估计值 x 出发,就能用下面的公式算出更好的估计值:

y = (x + a/x) / 2

y = (x + a/x) / 2

For example, if a is 4 and x is 3:

例如,若 a 为 4,x 为 3:
5 7000120

Which is closer to the correct answer (√4 = 2). If we repeat the process with the new estimate, it gets even closer:

这已经更接近正确答案(√4 = 2)。如果用新的估计值重复这个过程,会更接近:
5 7000121

After a few more updates, the estimate is almost exact:

再更新几次之后,估计值就几乎精确了:
5 7000122

In general we don’t know ahead of time how many steps it takes to get to the right answer, but we know when we get there because the estimate stops changing:

一般来说,我们事先不知道需要多少步才能得到正确答案,但当估计值不再变化时,我们就知道到达了:
5 7000123

When 5 7000, we can stop. Here is a loop that starts with an initial estimate, =, and improves it until it stops changing:

5 7000 时,我们就可以停止。下面这个循环以一个初始估计值 = 开始,不断改进它,直到不再变化:
5 7000128

For most values of = this works fine, but in general it is dangerous to test 5 700 equality. Floating-point values are only approximately right: most rational numbers, like 1/3, and irrational numbers, like √2, can’t be represented exactly with a 5 700.

对大多数 = 值,这都能正常工作,但一般地,直接比较 5 700 是否相等是有风险的。浮点值只是近似正确:大多数有理数(如 1/3)和无理数(如 √2)都无法用 5 700 精确表示。

Rather than checking whether = and = are exactly equal, it is safer to use the built-in function 5 7 to compute the absolute value, or magnitude, of the difference between them:

与其检查 == 是否完全相等,更安全的做法是使用内建函数 5 7 计算两者之间差值的绝对值(或者说大小):
5 7000141

Where 5 70001 has a value like 5 7000143 that determines how close is close enough.

其中 5 70001 取类似 5 7000145 的值,用来决定「足够接近」的判定标准。

Exercise 2

习题 2

Encapsulate this loop in a function called 5 7000146 that takes = as a parameter, chooses a reasonable value of =, and returns an estimate of the square root of =.

把这个循环封装成一个名为 5 7000150 的函数,它以 = 为形参,选取一个合理的 = 值,并返回 = 的平方根的估计值。

7.6 Algorithms 7.6 算法

Newton’s method is an example of an algorithm: it is a mechanical process for solving a category of problems (in this case, computing square roots).

牛顿法就是算法的一个例子:它是求解某一类问题(此处是计算平方根)的机械过程。

It is not easy to define an algorithm. It might help to start with something that is not an algorithm. When you learned to multiply single-digit numbers, you probably memorized the multiplication table. In effect, you memorized 100 specific solutions. That kind of knowledge is not algorithmic.

给算法下定义并不容易。不妨先从不算算法的东西说起。你小时候学一位数乘法时,多半是背了乘法表。实际上,你记住了 100 个具体的解法。这类知识不是算法性的。

But if you were “lazy,” you probably cheated by learning a few tricks. For example, to find the product of n and 9, you can write n−1 as the first digit and 10−n as the second digit. This trick is a general solution for multiplying any single-digit number by 9. That’s an algorithm!

但如果你很「懒」,大概会耍点小聪明、记几个诀窍。例如,要计算 n 与 9 的乘积,可以把 n−1 写作第一位,把 10−n 写作第二位。这个诀窍是把任意一位数乘以 9 的通用解法。这就是算法!

Similarly, the techniques you learned for addition with carrying, subtraction with borrowing, and long division are all algorithms. One of the characteristics of algorithms is that they do not require any intelligence to carry out. They are mechanical processes in which each step follows from the last according to a simple set of rules.

同理,你学过的进位加法、借位减法、长除法都是算法。算法的特点之一是:执行它不需要任何智能。它们是机械的过程,每一步都依据简单的规则从上一步推出。

In my opinion, it is embarrassing that humans spend so much time in school learning to execute algorithms that, quite literally, require no intelligence.

依我看,人类在学校里花大量时间学习执行那些压根不需要智能的算法,实在有些尴尬。

On the other hand, the process of designing algorithms is interesting, intellectually challenging, and a central part of what we call programming.

另一方面,设计算法的过程则妙趣横生、富有智性挑战,也是我们所谓「编程」的核心部分。

Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically. Understanding natural language is a good example. We all do it, but so far no one has been able to explain how we do it, at least not in the form of an algorithm.

人类能自然而然、毫不费力、不加思索地完成的一些事,反而最难用算法表达。理解自然语言就是个好例子。我们人人都会,但至今没有人能解释我们究竟是怎么做到的——至少没法用算法形式解释。

7.7 Debugging 7.7 调试

As you start writing bigger programs, you might find yourself spending more time debugging. More code means more chances to make an error and more place for bugs to hide.

随着你开始编写更大的程序,可能会发现自己花在调试上的时间越来越多。代码越多,出错的机会越多,缺陷藏身之处也越多。

One way to cut your debugging time is “debugging by bisection.” For example, if there are 100 lines in your program and you check them one at a time, it would take 100 steps.

缩短调试时间的一种方法是「二分调试法」。例如,如果你的程序有 100 行,逐行检查就需要 100 步。

Instead, try to break the problem in half. Look at the middle of the program, or near it, for an intermediate value you can check. Add a 5 700 statement (or something else that has a verifiable effect) and run the program.

不如试着把问题一分为二。找到程序的中间(或接近中间)位置,查看某个可以检查的中间值。加一条 5 700 语句(或任何有可验证效果的东西),然后运行程序。

If the mid-point check is incorrect, there must be a problem in the first half of the program. If it is correct, the problem is in the second half.

如果中点检查不正确,那么问题一定在前半部分;如果正确,问题就在后半部分。

Every time you perform a check like this, you halve the number of lines you have to search. After six steps (which is fewer than 100), you would be down to one or two lines of code, at least in theory.

每做一次这样的检查,要搜索的代码行数就减半。经过六步(少于 100),理论上就能把范围缩小到一两行代码。

In practice it is not always clear what the “middle of the program” is and not always possible to check it. It doesn’t make sense to count lines and find the exact midpoint. Instead, think about places in the program where there might be errors and places where it is easy to put a check. Then choose a spot where you think the chances are about the same that the bug is before or after the check.

实践中,「程序的中间」并不总是明确,检查也并非总可行。逐行数到精确的中点毫无意义。不如想想程序中可能出错的地方,以及便于插入检查的地方。然后选一个你认为缺陷在检查点之前或之后的概率大致相等的点。

7.8 Glossary 7.8 术语表

multiple assignment:
Making more than one assignment to the same variable during the execution of a program.
update:
An assignment where the new value of the variable depends on the old.
initialization:
An assignment that gives an initial value to a variable that will be updated.
increment:
An update that increases the value of a variable (often by one).
decrement:
An update that decreases the value of a variable.
iteration:
Repeated execution of a set of statements using either a recursive function call or a loop.
infinite loop:
A loop in which the terminating condition is never satisfied.
multiple assignment 多重赋值:
在执行程序的过程中,对同一个变量进行多次赋值。
update 更新:
一种赋值,变量的新值依赖于旧值。
initialization 初始化:
给一个将要被更新的变量赋予初始值的赋值。
increment 递增:
使变量值增大的更新(通常加一)。
decrement 递减:
使变量值减小的更新。
iteration 迭代:
使用递归函数调用或循环,对一组语句反复执行。
infinite loop 无限循环:
终止条件永远无法满足的循环。

7.9 Exercises 7.9 习题

Exercise 3

习题 3

To test the square root algorithm in this chapter, you could compare it with 5 7000156. Write a function named 5 7000157 that prints a table like this:

要测试本章的平方根算法,你可以把它和 5 7000158 对比。写一个名叫 5 7000159 的函数,打印出如下表:
5 7000160

The first column is a number, a; the second column is the square root of a computed with the function from Section 7.5; the third column is the square root computed by 5 7000161; the fourth column is the absolute value of the difference between the two estimates.

第一列是一个数 a;第二列是用 7.5 节的函数计算的 a 的平方根;第三列是用 5 7000162 计算的平方根;第四列是两个估计值之差的绝对值。

Exercise 4

习题 4

The built-in function 5 70 takes a string and evaluates it using the Python interpreter. For example:

内建函数 5 70 接受一个字符串,并用 Python 解释器对它求值。例如:
5 7000165

Write a function called 5 7000166 that iteratively prompts the user, takes the resulting input and evaluates it using 5 70, and prints the result.

写一个名为 5 7000168 的函数,循环提示用户输入,取出输入内容并用 5 70 求值,然后打印结果。

It should continue until the user enters 5 7000, and then return the value of the last expression it evaluated.

它应当一直进行,直到用户输入 5 7000,然后返回它所求值的最后一个表达式的值。

Exercise 5

习题 5

The mathematician Srinivasa Ramanujan found an infinite series that can be used to generate a numerical approximation of 1 / π:

数学家斯里尼瓦瑟·拉马努金发现了一个无穷级数,可用来生成 1/π 的数值近似:

1/π = (2√2 / 9801) Σk=0 (4k)!(1103 + 26390k) / ((k!)4 3964k)

1/π = (2√2 / 9801) Σk=0 (4k)!(1103 + 26390k) / ((k!)4 3964k)

Write a function called 5 7000172 that uses this formula to compute and return an estimate of π. It should use a 5 700 loop to compute terms of the summation until the last term is smaller than 5 700 (which is Python notation for 10−15). You can check the result by comparing it to 5 70001.

写一个名为 5 7000176 的函数,用这个公式计算出 π 的估计值并返回。它应当使用 5 700 循环来计算求和的每一项,直到最后一项小于 5 700(这是 10−15 的 Python 记法)。你可以把它和 5 70001 对比来验证结果。

Solution: 5 7000180.

参考解答:5 7000181。