# Chapter 6 Fruitful functions 有返回值的函數
Many of the Python functions we have used, such as the math functions, produce return values. But the functions we’ve written are all void: they have an effect, like printing a value or moving a turtle, but they don’t have a return value. In this chapter you will learn to write fruitful functions.
> 我們已經用過的很多Python的函數,比如數學函數,都會有返回值。但我們寫過的函數都是無返回值的:他們實現一些效果,比如輸出一些值,或者移動小烏龜,但他們就是不返回值。
## 6.1 Return values 返回值
Calling the function generates a return value, which we usually assign to a variable or use as part of an expression.
> 對函數進行調用,就會產生一個返回的值,我們一般把這個值賦給某個變量,或者放進表達式中來用。
```Python
e = math.exp(1.0) height = radius * math.sin(radians)
```
The functions we have written so far are void. Speaking casually, they have no return value; more precisely, their return value is None.
> 目前為止,我們寫過的函數都沒有返回值。簡單說是沒有返回值,更確切的講,這些函數的返回值是空(None)。
In this chapter, we are (finally) going to write fruitful functions. The first example is area, which returns the area of a circle with the given radius:
> 在本章,我們總算要寫一些有返回值的函數了。第一個例子就是一個計算給定半徑的圓的面積的函數:
```Python
def area(radius):
a = math.pi * radius**2
return a
```
We have seen the return statement before, but in a fruitful function the return statement includes an expression. This statement means: “Return immediately from this function and use the following expression as a return value.” The expression can be arbitrarily complicated, so we could have written this function more concisely:
> 返回語句我們之前已經遇到過了,但在有返回值的函數里面,返回語句可以包含表達式。這個返回語句的意思是:立即返回下面這個表達式作為返回值。返回語句里面的表達式可以隨便多復雜都行,所以剛剛那個計算面積的函數我們可以精簡改寫成以下形式:
```Python
def area(radius):
return math.pi * radius**2
```
On the other hand, temporary variables like a can make debugging easier.
Sometimes it is useful to have multiple return statements, one in each branch of a conditional:
> 另外,有一些臨時變量可以讓后續的調試過程更簡單。所以有時候可以多設置幾條返回語句,每一條都對應一種情況。
```Python
def absolute_value(x):
if x < 0:
return -x
else:
return x
```
Since these return statements are in an alternative conditional, only one runs.
> 因為這些返回語句對應的是不同條件,因此實際上最終只會有一個返回動作執行。
As soon as a return statement runs, the function terminates without executing any subsequent statements. Code that appears after a return statement, or any other place the flow of execution can never reach, is called dead code.
In a fruitful function, it is a good idea to ensure that every possible path through the program hits a return statement. For example:
> 返回語句運行的時候,函數就結束了,也不會運行任何其他的語句了。返回語句后面的代碼,執行流程里所有其他的位置都無法再觸碰了,這種代碼叫做『死亡代碼』。在有返回值的函數里面,建議要確認一下每一種存在的可能,來讓函數觸發一個返回語句。下面例子中:
```Python
def absolute_value(x):
if x < 0:
return -x
if x > 0:
return x
```
This function is incorrect because if x happens to be 0, neither condition is true, and the function ends without hitting a return statement. If the flow of execution gets to the end of a function, the return value is None, which is not the absolute value of 0.
> 這個函數就是錯誤的,因為一旦x等于0了,兩個條件都沒滿足,沒有觸發返回語句,函數就結束了。執行流程走完這個函數之后,返回的就是空(None),而本應該返回0的絕對值的。
```Python
>>> absolute_value(0)
>>> absolute_value(0)
None
```
By the way, Python provides a built-in function called abs that computes absolute values.
As an exercise, write a compare function takes two values, x and y, and returns 1 if x > y, 0 if x == y, and -1 if x < y.
> 順便說一下,Python內置函數就有一個叫abs的,就是用來求絕對值的。
> 然后練習一下把,寫一個比較大小的函數,用兩個之x和y作為參數,如果x大于y返回1,相等返回0,x小于y返回-1.
## 6.2 Incremental development 增量式開發
As you write larger functions, you might find yourself spending more time debugging.
To deal with increasingly complex programs, you might want to try a process called incremental development. The goal of incremental development is to avoid long debugging sessions by adding and testing only a small amount of code at a time.
As an example, suppose you want to find the distance between two points, given by the coordinates (x1, y1) and (x2, y2). By the Pythagorean theorem, the distance is:
> 寫一些復雜函數的時候,你會發現要花很多時間調試。
> 要應對越來越復雜的程序,你不妨來試試增量式開發的辦法。增量式開發的目的是避免長時間的調試過程,一點點對已有的小規模代碼進行增補和測試。
```
distance = \sqrt{(x_2 ? x_1)^2 + (y_2 ? y_1)^2}
```
The first step is to consider what a distance function should look like in Python. In other words, what are the inputs (parameters) and what is the output (return value)?
> 首先大家來想一下用Python來計算兩點距離的函數應該是什么樣。換句話說,輸入的參數是什么,輸出的返回值是什么?
In this case, the inputs are two points, which you can represent using four numbers. The return value is the distance represented by a floating-point value.
Immediately you can write an outline of the function:
> 這個案例里面,輸入的應該是兩個點的坐標,平面上就是四個數字了。返回的值是兩點間的距離,就是一個浮點數了。
```Python
def distance(x1, y1, x2, y2):
return 0.0
```
Obviously, this version doesn’t compute distances; it always returns zero. But it is syntactically correct, and it runs, which means that you can test it before you make it more complicated.
To test the new function, call it with sample arguments:
> 當然了,上面這個版本的代碼肯定算不出距離了;不管輸入什么都會返回0了。但這個函數語法上正確,而且可以運行,這樣在程序過于復雜的情況之前就能及時測試了。
> 要測試一下這個新函數,就用簡單的參數來調用一下吧:
```Python
>>> distance(1, 2, 4, 6)
>>> distance(1, 2, 4, 6)
0.0
```
I chose these values so that the horizontal distance is 3 and the vertical distance is 4; that way, the result is 5, the hypotenuse of a 3-4-5 triangle. When testing a function, it is useful to know the right answer.
> 我選擇這些數值,水平的距離就是3,豎直距離就是4;這樣的話,平面距離就應該是5了,是一個3-4-5三角形的斜邊長了。我們已經知道正確結果應該是什么了,這樣對測試來說很有幫助。
At this point we have confirmed that the function is syntactically correct, and we can start adding code to the body. A reasonable next step is to find the differences x2 ? x1 and y2 ? y1. The next version stores those values in temporary variables and prints them.
> 現在我們已經確認過了,這個函數在語法上是正確的,接下來我們就可以在函數體內添加代碼了。下一步先添加一下求x2-x1和y2-y1的值的內容。接下來的版本里面,就把它們存在一些臨時變量里面,然后輸出一下。
```Python
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
print('dx is', dx)
print('dy is', dy)
return 0.0
```
If the function is working, it should display 'dx is 3' and 'dy is 4'. If so, we know that the function is getting the right arguments and performing the first computation correctly. If not, there are only a few lines to check.
Next we compute the sum of squares of dx and dy:
> 這個函數如果工作的話,應該顯示出'dx is 3'和'dy is 4'。如果成功顯示了,我們就知道函數已經得到了正確的實際參數,并且正確進行了初步的運算。如果沒有顯示,只要檢查一下這么幾行代碼就可以了。接下來,就要計算dx和dy的平方和了。
```Python
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
print('dsquared is: ', dsquared)
return 0.0
```
Again, you would run the program at this stage and check the output (which should be 25). Finally, you can use math.sqrt to compute and return the result:
> 在這一步,咱們依然虧運行一下程序,來檢查輸出,輸出的應該是25。輸出正確的話,最后一步就是用math.sqrt這個函數來計算并返回結果:
```Python
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
result = math.sqrt(dsquared)
return result
```
If that works correctly, you are done. Otherwise, you might want to print the value of result before the return statement.
> 如果程序工作沒問題,就搞定了。否則你可能就需要用print輸出一下最終計算結果,然后再返回這個值。
The final version of the function doesn’t display anything when it runs; it only returns a value. The print statements we wrote are useful for debugging, but once you get the function working, you should remove them. Code like that is called scaffolding because it is helpful for building the program but is not part of the final product.
> 此函數的最終版本在運行的時候是不需要顯示任何內容的;這個函數只需要返回一個值。我們寫得這些print打印語句都是用來調試的,但一旦程序能正常工作了,就應該把print語句去掉。這些print代碼也叫『腳手架代碼』因為是用來構建程序的,但不會被存放在最終版本的程序中。
When you start out, you should add only a line or two of code at a time. As you gain more experience, you might find yourself writing and debugging bigger chunks. Either way, incremental development can save you a lot of debugging time.
The key aspects of the process are:
> 當你動手的時候,每次建議只添加一兩行代碼。等你經驗更多了,你發現自己可能就能夠駕馭大塊代碼了。不論如何,增量式開發總還是能幫你節省很多調試消耗的時間。
> 這個過程的核心如下:
1. Start with a working program and make small incremental changes. At any point, if there is an error, you should have a good idea where it is.
> 一定要用一個能工作的程序來開始,每次逐漸添加一些細小增補。在任何時候遇到錯誤,都應該弄明白錯誤的位置。
2. Use variables to hold intermediate values so you can display and check them.
> 用一些變量來存儲中間值,這樣你可以顯示一下這些值,來檢查一下。
3. Once the program is working, you might want to remove some of the scaffolding or consolidate multiple statements into compound expressions, but only if it does not make the program difficult to read.
> 程序一旦能工作了,你就應該把一些發揮『腳手架作用』的代碼刪掉,并且把重復的語句改寫成精簡版本,但盡量別讓程序變得難以閱讀。
As an exercise, use incremental development to write a function called hypotenuse that returns the length of the hypotenuse of a right triangle given the lengths of the two legs as arguments. Record each stage of the development process as you go.
> 做個練習吧,用這種增量式開發的思路來寫一個叫做hypotenuse(斜邊)的函數,接收兩個數值作為給定兩邊長,求以兩邊長為直角邊的直角三角形斜邊的長度。做練習的時候記得要記錄好開發的各個階段。
## 6.3 Composition 組合
As you should expect by now, you can call one function from within another. As an example, we’ll write a function that takes two points, the center of the circle and a point on the perimeter, and computes the area of the circle.
Assume that the center point is stored in the variables xc and yc, and the perimeter point is in xp and yp. The first step is to find the radius of the circle, which is the distance between the two points. We just wrote a function,distance, that does that:
> 你現在應該已經能夠在一個函數里面調用另外一個函數了。下面我們寫一個函數作為例子,這個函數需要兩個點,一個是圓心,一個是圓周上面的點,函數要用來計算這個圓的面積。
> 假設圓心的坐標存成一對變量:xc和yc,圓周上一點存成一對變量:xp和yp。第一步就是算出來這個圓的半徑,也就是這兩個點之間的距離。我們就用之前寫過的那個distance的函數來完成這件事:
```Python
radius = distance(xc, yc, xp, yp)
```
The next step is to find the area of a circle with that radius; we just wrote that, too:
> 下一步就是根據計算出來的半徑來算圓的面積;計算面積的函數我們也寫過了:
```Python
result = area(radius)
```
Encapsulating these steps in a function, we get:
> 把上述的步驟組合在一個函數里面:
```Python
def circle_area(xc, yc, xp, yp):
radius = distance(xc, yc, xp, yp)
result = area(radius)
return result
```
The temporary variables radius and result are useful for development and debugging, but once the program is working, we can make it more concise by composing the function calls:
> 臨時變量radius和result是用于開發和調試用的,只要程序能正常工作了,就可以把它們都精簡下去:
```Python
def circle_area(xc, yc, xp, yp):
return area(distance(xc, yc, xp, yp))
```
## 6.4 Boolean functions 布爾函數
Functions can return booleans, which is often convenient for hiding complicated tests inside functions. For example:
> 函數也可以返回布爾值,這種情況便于隱藏函數內部的復雜測試。例如:
```Python
def is_divisible(x, y):
if x % y == 0:
return True
else:
return False
```
It is common to give boolean functions names that sound like yes/no questions; is_divisible returns either True or False to indicate whether x is divisible by y.
Here is an example:
> 一般情況下都給這種布爾函數起個獨特的名字,比如要有判斷意味的提示;is_divisible這個函數就去判斷x能否被y整除而對應地返回真或假。
```Python
>>> is_divisible(6, 4)
>>> is_divisible(6, 4)
False
>>> is_divisible(6, 3)
>>> is_divisible(6, 3)
True
```
The result of the == operator is a boolean, so we can write the function more concisely by returning it directly:
> 雙等號運算符的返回結果是一個布爾值,所以我們可以用下面的方法來簡化剛剛的函數:
```Python
def is_divisible(x, y):
return x % y == 0
```
Boolean functions are often used in conditional statements:
> 布爾函數經常用于條件語句:
```Python
if is_divisible(x, y):
print('x is divisible by y')
```
It might be tempting to write something like:
> 可以用于寫如下這種代碼:
```Python
if is_divisible(x, y) == True:
print('x is divisible by y'
```
But the extra comparison is unnecessary.
As an exercise, write a function is_between(x, y, z) that returns True if x ≤ y ≤z or False otherwise.
> 但額外的比較并沒有必要。
> 做一個練習,寫一個函數is_between(x, y, z),如果x ≤ y ≤z則返回真,其他情況返回假。
## 6.5 More recursion 更多遞歸
We have only covered a small subset of Python, but you might be interested to know that this subset is a complete programming language, which means that anything that can be computed can be expressed in this language. Any program ever written could be rewritten using only the language features you have learned so far (actually, you would need a few commands to control devices like the mouse, disks, etc., but that’s all).
> 我們目前學過的知識Python的一小部分子集,不過這部分子集本身已經是一套完整的編程語言了,這就意味著所有能計算的東西都可以用這部分子集來表達。實際上任何程序都可以改寫成只用你所學到的這部分Python特性的代碼。(當然你還需要一些額外的代碼來控制設備,比如鼠標、磁盤等等,但也就這么多額外需要而已。)
Proving that claim is a nontrivial exercise first accomplished by Alan Turing, one of the first computer scientists (some would argue that he was a mathematician, but a lot of early computer scientists started as mathematicians). Accordingly, it is known as the Turing Thesis. For a more complete (and accurate) discussion of the Turing Thesis, I recommend Michael Sipser’s book Introduction to the Theory of Computation.
> 阿蘭圖靈最先證明了這個說法,他是最早的計算機科學家之一,有人會認為他更是一個數學家,不過早起的計算機科學家也都是作為數學家來起步的。因此這個觀點也被叫做圖靈理論。關于對此更全面也更準確的討論,我推薦一本Michael Sipser的書:Introduction to the Theory of Computation 計算方法導論。
To give you an idea of what you can do with the tools you have learned so far, we’ll evaluate a few recursively defined mathematical functions. A recursive definition is similar to a circular definition, in the sense that the definition contains a reference to the thing being defined. A truly circular definition is not very useful:
> 為了讓你能更清晰地認識到自己當前學過的這些內容能用來做什么,我們會看看一些數學的函數,這些函數都是遞歸定義的。遞歸定義與循環定義有些相似,就是函數的定義體內包含了對所定義內容的引用。一一個完全循環的定義并不會有太大用。
vorpal:
An adjective used to describe something that is vorpal.
> 刺穿的:用來描述被刺穿的形容詞。
If you saw that definition in the dictionary, you might be annoyed. On the other hand, if you looked up the definition of the factorial function, denoted with the symbol !, you might get something like this:
> 你在詞典里面看到上面這種定義,一定很郁悶。然而如果你查一下階乘函數的定義,你估計會得到如下結果:
0! = 1
n! = n (n?1)!
This definition says that the factorial of 0 is 1, and the factorial of any other value, n, is n multiplied by the factorial of n?1.
> 這個定義表明了0的階乘為1,然后對任意一個數n的階乘,是n與n-1階乘相乘。
So 3! is 3 times 2!, which is 2 times 1!, which is 1 times 0!. Putting it all together, 3! equals 3 times 2 times 1 times 1, which is 6.
> 所以3的階乘就是3乘以2的階乘,而2的階乘就是2乘以1的階乘,1的階乘是1乘以0的階乘。算到一起,3的階乘就等于3*2*1*1,就是6了。
If you can write a recursive definition of something, you can write a Python program to evaluate it. The first step is to decide what the parameters should be. In this case it should be clear that factorial takes an integer:
> 如果要給某種對象寫一個遞歸的定義,就可以用Python程序來實現。第一步是要來確定形式參數是什么。在這種情況下要明確階乘所用到的都應該是整形:
```Python
def factorial(n):
```
If the argument happens to be 0, all we have to do is return 1:
> 如果傳來的實際參數碰巧是0,要返回1:
```Python
def factorial(n):
if n == 0:
return 1
```
Otherwise, and this is the interesting part, we have to make a recursive call to find the factorial of n?1 and then multiply it by n:
> 其他情況就有意思了,我們必須用遞歸的方式來調用n-1的階乘,然后用它來乘以n:
```Python
def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result
```
The flow of execution for this program is similar to the flow of countdown in Section 5.8. If we call factorial with the value 3:
> 這個程序的運行流程和5.8里面的那個倒計時有點相似。我們用3作為參數來調用一下這個階乘函數試試:
Since 3 is not 0, we take the second branch and calculate the factorial of n-1...
> 3不是0,所以分支一下,計算n-1的階乘。。。
Since 2 is not 0, we take the second branch and calculate the factorial of n-1...
> 2不是0,所以分支一下,計算n-1的階乘。。。
Since 1 is not 0, we take the second branch and calculate the factorial of n-1...
> 1不是0,所以分支一下,計算n-1的階乘。。。
Since 0 equals 0, we take the first branch and return 1 without making any more recursive calls.
> 到0了,就返回1給進行遞歸的分支。
The return value, 1, is multiplied by n, which is 1, and the result is returned.
> 返回值1與1相乘,結果再次返回。
The return value, 1, is multiplied by n, which is 2, and the result is returned.
> 返回值1與2相乘,結果再次返回。
The return value (2) is multiplied by n, which is 3, and the result, 6, becomes the return value of the function call that started the whole process.
> 2的返回值再與n也就是3想成,得到的結果是6,就成了整個流程最終得到的答案。
Figure 6.1 shows what the stack diagram looks like for this sequence of function calls.
> 圖6.1表明了這一系列函數調用過程中的棧圖。
* * *

Figure 6.1: Stack diagram.
* * *
The return values are shown being passed back up the stack. In each frame, the return value is the value of result, which is the product of n and recurse.
In the last frame, the local variables recurse and result do not exist, because the branch that creates them does not run.
>
## 6.6 Leap of faith 思維跳躍
Following the flow of execution is one way to read programs, but it can quickly become overwhelming. An alternative is what I call the “leap of faith”. When you come to a function call, instead of following the flow of execution, you assume that the function works correctly and returns the right result.
> 跟隨著運行流程是閱讀程序的一種方法,但很快就容易弄糊涂。另外一個方法,我稱之為『思維跳躍』。當你遇到一個函數調用的時候,你不用去追蹤具體的執行流程,而是假設這個函數工作正常并且返回正確的結果。
In fact, you are already practicing this leap of faith when you use built-in functions. When you call math.cos or math.exp, you don’t examine the bodies of those functions. You just assume that they work because the people who wrote the built-in functions were good programmers.
> 實際上你已經聯系過這種思維跳躍了,就在你使用內置函數的時候。當你調用math.cos或者math.exp的時候,你并沒有仔細查看這些函數的函數體。你就假設他們都工作,因為寫這些內置函數的人都是很可靠的編程人員。
The same is true when you call one of your own functions. For example, in Section 6.4, we wrote a function called is_divisible that determines whether one number is divisible by another. Once we have convinced ourselves that this function is correct—by examining the code and testing—we can use the function without looking at the body again.
> 你調用自己寫的函數時候也是同樣道理。比如在6.4部分,我們謝了這個叫做is_divisible的函數來判斷一個數能否被另外一個數整除。一旦我們通過分析代碼和做測試來確定了這個函數沒有問題,我們就可以直接使用這個函數了,不用去理會函數體內部細節了。
The same is true of recursive programs. When you get to the recursive call, instead of following the flow of execution, you should assume that the recursive call works (returns the correct result) and then ask yourself, “Assuming that I can find the factorial of n?1, can I compute the factorial of n?” It is clear that you can, by multiplying by n.
> 對于遞歸函數而言也是同樣道理。當你進行遞歸調用的時候,并不用追蹤執行流程,你只需要假設遞歸調用正常工作,返回正確結果,然后你可以問問自己:『假設我能算出來n-1的階乘,我能否計算出n的階乘呢?』很顯然你是可以的,乘以n就可以了。
Of course, it’s a bit strange to assume that the function works correctly when you haven’t finished writing it, but that’s why it’s called a leap of faith!
> 當然了,當你還沒寫完一個函數的時候就假設它正常工作確實有點奇怪,不過這也是我們稱之為『思維飛躍』的原因了,你總得飛躍一下。
## 6.7 One more example 斐波拉契數列
After factorial, the most common example of a recursively defined mathematical function is fibonacci, which has the following definition (see [Here](http://en.wikipedia.org/wiki/Fibonacci_number) ):
> 計算階乘之后,我們來看看斐波拉契數列,這是一個廣泛應用于展示遞歸定義的數學函數,[定義](http://en.wikipedia.org/wiki/Fibonacci_number如下:
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n?1) + fibonacci(n?2)
Translated into Python, it looks like this:
> 翻譯成Python的語言大概如下這樣:
```Python
def fibonacci (n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
```
If you try to follow the flow of execution here, even for fairly small values of n, your head explodes. But according to the leap of faith, if you assume that the two recursive calls work correctly, then it is clear that you get the right result by adding them together.
> 這里如果你還想嘗試著追蹤執行流程,你一定得累死,即便是一些很小的n,你都得頭炸掉了。但根據『思維飛躍』的方法,如果你假設兩個遞歸調用都正常工作,整個過程就很明確了,你就得到正確答案加到一起即可。
## 6.8 Checking types 檢查類型
What happens if we call factorial and give it 1.5 as an argument?
> 如果我們讓階乘使用1.5做參數會咋樣?
```Python
>>> factorial(1.5)
>>> factorial(1.5)
RuntimeError: Maximum recursion depth exceeded
```
It looks like an infinite recursion. How can that be? The function has a base case—when n == 0. But if n is not an integer, we can miss the base case and recurse forever.
> 看上去就像是無窮遞歸一樣。為什么會這樣?因為這個函數的基準條件是n等于0。但如果n不是一個整形變量,就會無法達到基準條件,然后無窮遞歸下去。
In the first recursive call, the value of n is 0.5. In the next, it is -0.5. From there, it gets smaller (more negative), but it will never be 0.
We have two choices. We can try to generalize the factorial function to work with floating-point numbers, or we can make factorial check the type of its argument. The first option is called the gamma function and it’s a little beyond the scope of this book. So we’ll go for the second.
> 在第一次遞歸調用的時候,傳遞的n值是0.5.下一步就是-0.5.
> 從這開始,這個值就越來越小(就成了更小的負數了)但永遠都不會到0.
> 我們有兩種選擇。我們可以嘗試著把階乘函數改寫成可以用浮點數做參數的,或者也可以讓階乘函數檢查一下參數類型。第一種選擇就會寫出一個伽瑪函數,這已經超越了本書的范圍。所以我們用第二種方法。
(譯者注:伽瑪函數(Gamma函數),也叫歐拉第二積分,是階乘函數在實數與復數上擴展的一類函數。該函數在分析學、概率論、偏微分方程和組合數學中有重要的應用。與之有密切聯系的函數是貝塔函數,也叫第一類歐拉積分。)
We can use the built-in function isinstance to verify the type of the argument. While we’re at it, we can also make sure the argument is positive:
> 我們可以用內置的isinstance函數來判斷參數的類型。我們也還得確定一下參數得是大于0的:
```Python
def factorial (n):
if not isinstance(n, int):
print('Factorial is only defined for integers.')
return None
elif n < 0:
print('Factorial is not defined for negative integers.')
return None
elif n == 0:
return 1
else:
return n * factorial(n-1)
```
The first base case handles nonintegers; the second handles negative integers. In both cases, the program prints an error message and returns None to indicate that something went wrong:
> 第一個基準條件用來處理非整數;第二個用來處理負整數。在小數或者負數做參數的時候,函數就會輸出錯誤信息,返回空到調用出來表明出錯了:
```Python
>>> factorial('fred')
>>> factorial('fred')
Factorial is only defined for integers. None
>>> factorial(-2)
>>> factorial(-2)
Factorial is not defined for negative integers. None
```
If we get past both checks, we know that n is positive or zero, so we can prove that the recursion terminates.
> 如果兩個檢查都通過了,就能確定n是正整數或者0,就可以保證遞歸能夠正確進行和終止了。
This program demonstrates a pattern sometimes called a guardian. The first two conditionals act as guardians, protecting the code that follows from values that might cause an error. The guardians make it possible to prove the correctness of the code.
> 這個程序展示了一種叫做『守衛』的模式。前兩個條件就扮演了守衛的角色,避免了那些引起錯誤的變量。這些守衛能夠保證我們的代碼運行正確。
In Section 11.4 we will see a more flexible alternative to printing an error message: raising an exception.
> 在11.4我們還會看到更多的靈活的處理方式,會輸出錯誤信息,并上報異常。
## 6.9 Debugging 調試
Breaking a large program into smaller functions creates natural checkpoints for debugging. If a function is not working, there are three possibilities to consider:
> 把大的程序切分成小塊的函數,就自然為我們調試建立了一個個的檢查點。在不工作的函數里面,有幾種導致錯誤的可能:
* There is something wrong with the arguments the function is getting; a precondition is violated.
> 函數接收的參數可能有錯,前置條件沒有滿足。
* There is something wrong with the function; a postcondition is violated.
> 函數本身可能有錯,后置條件沒有滿足。
? There is something wrong with the return value or the way it is being used.
> 返回值或者返回值使用的方法可能有錯。
To rule out the first possibility, you can add a print statement at the beginning of the function and display the values of the parameters (and maybe their types). Or you can write code that checks the preconditions explicitly.
If the parameters look good, add a print statement before each return statement and display the return value. If possible, check the result by hand. Consider calling the function with values that make it easy to check the result (as in Section 6.2).
> 要去除第一種情況,你要在函數開頭的時候添加一個print語句,來輸出一下參數的值(最好加上類型)。或者你可以寫一份代碼來明確檢查一下前置條件是否滿足。
> 如果形式參數看上去沒問題,在每一個返回語句之前都加上print語句,顯示一下要返回的值。如果可以的話,盡量親自去檢查一下這些結果,自己算一算。盡量調用有值的函數,這樣檢查結果更方便些(比如在6.2中那樣。)
If the function seems to be working, look at the function call to make sure the return value is being used correctly (or used at all!).
Adding print statements at the beginning and end of a function can help make the flow of execution more visible. For example, here is a version of factorial with print statements:
> 如果函數看著沒啥問題,就檢查一下函數的調用,來檢查一下返回值是不是有用到,確保返回值被正確使用。
> 在函數的開頭結尾添加輸出語句,能夠確保整個執行流程更加可視化。比如下面就是一個有輸出版本的階乘函數:
```Python
def factorial(n):
space = ' ' * (4 * n)
print(space, 'factorial', n)
if n == 0:
print(space, 'returning 1')
return 1
else:
recurse = factorial(n-1)
result = n * recurse
print(space, 'returning', result)
return result
```
space is a string of space characters that controls the indentation of the output. Here is the result of factorial(4) :
> space在這里是一串空格的字符串,是用來縮進輸出的。下面就是4的階乘得到的結果:
```Python
factorial 4
factorial 3
factorial 2
factorial 1
factorial 0
returning 1
returning 1
returning 2
returning 6
returning 24
```
If you are confused about the flow of execution, this kind of output can be helpful. It takes some time to develop effective scaffolding, but a little bit of scaffolding can save a lot of debugging.
> 如果你對執行流程比較困惑,這種輸出會有一定幫助。有效率地進行腳手架開發是需要時間的,但稍微利用一下這種思路,反而能夠節省調試用的時間。
## 6.10 Glossary 術語列表
temporary variable:
A variable used to store an intermediate value in a complex calculation.
> 臨時變量:用來在復雜運算過程中存儲一些中間值的變量。
dead code:
Part of a program that can never run, often because it appears after a return statement.
> 無效代碼:一部分不會被運行的代碼,一般是書現在了返回語句之后。
incremental development:
A program development plan intended to avoid debugging by adding and testing only a small amount of code at a time.
> 漸進式開發:程序開發的一種方式,每次對已有的能工作的代碼進行小規模的增補修改來降低調試的精力消耗。
scaffolding:
Code that is used during program development but is not part of the final version.
> 腳手架代碼:在程序開發期間使用的代碼,但最終版本并不會包含這些代碼。
guardian:
A programming pattern that uses a conditional statement to check for and handle circumstances that might cause an error.
> 守衛:一種編程模式。使用一些條件語句來檢驗和處理一些有可能導致錯誤的情況。
## 6.11 Exercises 練習
### Exercise 1 練習1
Draw a stack diagram for the following program. What does the program print?
> 為下面的程序畫棧圖。程序輸出會是什么樣的?
```Python
def b(z):
prod = a(z, z)
print(z, prod)
return prod
def a(x, y):
x = x + 1
return x * y
def c(x, y, z):
total = x + y + z
square = b(total)**2
return square
x = 1
y = x + 1
print(c(x, y+3, x+y))
```
### Exercise 2 練習2
The Ackermann function, A(m, n), is defined:
> 阿克曼函數的定義如下:
```Python
A(m, n) = n+1 if m = 0
A(m?1, 1) if m > 0 and n = 0
A(m?1, A(m, n?1)) if m > 0 and n > 0.
```
See [Here](http://en.wikipedia.org/wiki/Ackermann_function). Write a function named ack that evaluates the Ackermann function. Use your function to evaluate ack(3, 4), which should be 125. What happens for larger values of m and n? [Solution](http://thinkpython2.com/code/ackermann.py).
> 看一下[這個連接](http://en.wikipedia.org/wiki/Ackermann_function)。寫一個叫做ack的函數,實現上面這個阿克曼函數。用你寫出的函數來計算ack(3, 4),結果應該是125.看看m和n更大一些會怎么樣。[樣例代碼](http://thinkpython2.com/code/ackermann.py).
### Exercise 3 練習3
A palindrome is a word that is spelled the same backward and forward, like “noon” and “redivider”. Recursively, a word is a palindrome if the first and last letters are the same and the middle is a palindrome.
> 回文的詞特點是正序和倒序拼寫相同給,比如noon以及redivider。用遞歸的思路來看,回文詞的收尾相同,中間部分是回文詞。
The following are functions that take a string argument and return the first, last, and middle letters:
> 下面的函數是把字符串作為實際參數,然后返回函數的頭部、尾部以及中間字母:
```Python
def first(word):
return word[0]
def last(word):
return word[-1]
def middle(word):
return word[1:-1]
```
We’ll see how they work in Chapter 8.
> 第八章我們再看看他們是到底怎么工作的。
1. Type these functions into a file named palindrome.py and test them out. What happens if you call middle with a string with two letters? One letter? What about the empty string, which is written '' and contains no letters?
> 把這些函數輸入到一個名字叫做palindrome.py的文件中,測試一下輸出。
> 如果中間你使用一個只有兩個字符的字符串會怎么樣?一個字符的怎么樣?
> 空字符串,比如『』沒有任何字母的,怎么樣?
2. Write a function called is_palindrome that takes a string argument and returns True if it is a palindrome and False otherwise. Remember that you can use the built-in function len to check the length of a string.
Solution: http://thinkpython2.com/code/palindrome_soln.py.
> 寫一個名叫is_palindrome的函數,使用字符串作為實際參數,根據字符串是否為回文詞來返回真假。機主,你可以用內置的len函數來檢查字符串的長度。
### Exercise 4 練習4
A number, a, is a power of b if it is divisible by b and a/b is a power of b. Write a function called is_power that takes parameters a and b and returns True if a is a power of b. Note: you will have to think about the base case.
> 一個數字a為b的權(power),如果a能夠被b整除,并且a/b是b的權。寫一個叫做is_power 的函數接收a和b作為形式參數,如果a是b的權就返回真。注意:要考慮好基準條件。
## Exercise 5 練習5
The greatest common divisor (GCD) of a and b is the largest number that divides both of them with no remainder.
> a和b的最大公約數是指能同時將這兩個數整除而沒有余數的數當中的最大值。
One way to find the GCD of two numbers is based on the observation that if r is the remainder when a is divided by b, then gcd(a, b) = gcd(b, r). As a base case, we can use gcd(a, 0) = a.
> 找最大公約數的一種方法是觀察,如果當r是a除以b的余數,那么a和b的最大公約數與b和r的最大公約數相等。基準條件是a和0的最大公約數為a。
Write a function called gcd that takes parameters a and b and returns their greatest common divisor.
> 寫一個有名叫gcd的函數,用a和b兩個形式參數,返回他們的最大公約數。
Credit: This exercise is based on an example from Abelson and Sussman’s Structure and Interpretation of Computer Programs.
> 致謝:這個練習借鑒了Abelson和Sussman的 計算機程序結構和解譯 一書。
- 介紹
- Think Python
- Chapter 0 Preface 前言
- Chapter 1 The way of the program 編程之路
- Chapter 2 Variables, expressions and statements 變量,表達式,語句
- Chapter 3 Functions 函數
- Chapter 4 Case study: interface design 案例學習:交互設計
- Chapter 5 Conditionals and recursion 條件循環
- Chapter 6 Fruitful functions 有返回值的函數
- Chapter 7 Iteration 迭代
- Chapter 8 Strings 字符串
- Chapter 9 Case study: word play 案例學習:單詞游戲
- Chapter 10 Lists 列表
- Chapter 11 Dictionaries 字典
- Chapter 12 Tuples 元組
- Chapter 13 Case study: data structure selection 案例學習:數據結構的選擇
- Chapter 14 Files 文件
- Chapter 15 Classes and objects 類和對象
- Chapter 16 Classes and functions 類和函數
- Chapter 17 Classes and methods 類和方法
- Chapter 18 Inheritance 繼承
- Chapter 19 The Goodies 額外補充