1. 程式人生 > >我要翻譯《Think Python》- 005 第三章 函式

我要翻譯《Think Python》- 005 第三章 函式

本文翻自:Allen B. Downey ——《Think Python》
原文連結:http://www.greenteapress.com/thinkpython/html/thinkpython004.html
翻譯:Simba Gu

[自述:感謝coneypo網友的建議,譯文的排版和書寫規範已經稍作調整,希望看起來能比前面翻譯的幾篇舒服一些 :)]

 

第三章 函式


3.1 函式呼叫

  在程式中,函式可以理解為一個有名字的執行命令的序列。當你在定義一個函式的時候,必須為其指定一個名字,然後再通過這個名字來呼叫。正如前一章裡面提到過的函式呼叫的例子:

>>> type(32)
<type 'int'>

  這裡的type就是一個函式,括號裡面的表示式叫做引數,它返回傳入引數的型別。通常來說就是函式“接收”值並“返回”結果,這個結果叫做返回值。

 

3.2 型別轉換函式

  Python內建了從一種型別轉換為其他型別的函式。例如 int 函式可以把任何能夠轉換成整數的值轉換成整數。

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello

  int 函式可以把浮點數轉換成整數,它會直接把小數部分剔除而非對轉換的浮點數進行四捨五入。

>>> int(3.99999)
3
>>> int(-2.3)
-2

  float 函式可以把整數或字串轉換成浮點數:

>>> float(32)
32.0
>>> float('3.14159')
3.14159

  最後,str 函式可以把引數轉換成字串

>>> str(32)
'32'
>>> str(3.14159)
'3.14159'

  

3.3 數學函式

  Python提供了一個包含大多數常用的數學函式的模組,這個模組是一個包含數學相關函式的集合的檔案,在使用裡面函式之前,需要用import命令匯入一下:

>>> import math

  這條語句表示建立了一個名為math的模組物件,你可以通過print函式來顯示這個模組的相關資訊:

>>> print math
<module 'math' (built-in)>

  次模組物件中包含了這個模組裡定義的所有函式和變數。訪問裡面包含的函式需要指定模組名稱和函式名稱,中間用“.”隔開,這種格式稱之為“點語法”。

>>> ratio = signal_power / noise_power
>>> decibels = 10 * math.log10(ratio)

>>> radians = 0.7
>>> height = math.sin(radians)

  第一個例子是呼叫log10函式以分貝為單位來計算信噪比(假設變數 signal_power 和 noise_power已經定義),math模組也提供了以log函式計算以e為底的對數。

  第二個例子是求radians的正弦值,從引數的名字我們還可以聯想到除了例子裡面出現的 sin 函式以外,還有其它的相關的三角函式,如 cos,tan 等等,這些函式都有一個弧度引數。

  角度轉換為弧度的公式為:角度 / 360 * 2 π:

>>> degrees = 45
>>> radians = degrees / 360.0 * 2 * math.pi
>>> math.sin(radians)
0.707106781187

  上面表示式中的 math.pi 表示從math 模組中獲取pi的值,它的值等於圓周率π的近似值,大概精確到15位小數。如果你想更多的瞭解三角函式,你可以拿前面例子的計算結果跟平方根除以2來驗證一下。

>>> math.sqrt(2) / 2.0
0.707106781187

  

3.4 組合

  到目前為止,我們已經初步瞭解了程式的基本元素:變數、表示式和語句,記下來我們將討論如何把它們組合起來。程式語言最實用的特性就是構建功能塊並組合起來實現特定功能。函式的引數可以是任何表示式,甚至可以是算術運算子,例如:

x = math.sin(degrees / 360.0 * 2 * math.pi)

  函式甚至還可以這樣呼叫:

x = math.exp(math.log(x+1))

  有一個需要注意的地方就是:變數名必須放在賦值語句的左邊,賦值語句右邊可以是任何表示式。如果表示式放在右邊將會觸發語法錯誤(後面我們將會了解到該異常的規則)。

>>> minutes = hours * 60 # right
>>> hours * 60 = minutes # wrong!
SyntaxError: can't assign to operator

 

3.5 建立新函式

  目前,我們只學習瞭如何使用Python內建的函式,當然我們也可以新增新的函式。函式定義需要指定一個函式名稱和函式呼叫執行的語句,例如:

def print_lyrics():
print "I'm a lumberjack, and I'm okay."
print "I sleep all night and I work all day."

  def 是表示定義一個函式的關鍵字,函式名稱為 print_lyrics。函式名的命名規則跟變數名的命名規則是一樣的:由字母、數字和一些合法的字元,當然首字元不能是數字,函式名不能跟關鍵字重名,另外還需要避免函式名跟變數名同名。函式名稱後面如果是一對空的括號則表示這個函式沒有引數。

  函式定義的第一行叫做頭,其餘部分叫做主體,函式頭必須以“:”結尾,並且函式體必須整體縮排。按慣例,縮排通常是4個空格(或一個tab縮排符),除此以外,函式體可以包含任意數量的語句。
  print語句中的字串可以用一對單引號或者一對雙引號括起來,大部分人習慣用單引號,但是如果引用的字串包含單引號(撇號),那就建議使用雙引號,反之亦然。如果你在互動模式下面定義函式,直譯器會列印省略號(...)來提醒你函式定義尚未結束:

>>> def print_lyrics():
... print "I'm a lumberjack, and I'm okay."
... print "I sleep all night and I work all day."
...

  在互動模式下,結束函式的定義必須輸入一個空行來告訴直譯器(在指令碼模式則無必要),另外結束函式定義之後,系統會自動建立一個同名變數。

>>> print print_lyrics
<function print_lyrics at 0xb7e99e9c>

>>> type(print_lyrics)
<type 'function'>

  由此可見 print_lyrics 是一個函式型別的函式物件,呼叫自定義函式的語法與呼叫內建函式的方法是一樣的: 

>>> print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

  當你定義好了一個函式,你也可以在其他函式裡面呼叫它。如果我們要重複列印print_lyrics函式的內容,我們可以寫一個新函式 repeat_lyrics 來實現這個功能:

def repeat_lyrics():
print_lyrics()
print_lyrics()

  當呼叫函式 repeat_lyrics 的時候你將會看到下面的結果:

>>> repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

     當然,這首歌實際上不是這麼唱的。

 

3.6 定義和引用

  將前面一節裡面的程式碼放在一起如下所示:

def print_lyrics():
print "I'm a lumberjack, and I'm okay."
print "I sleep all night and I work all day."

def repeat_lyrics():
print_lyrics()
print_lyrics()

repeat_lyrics()

  這段程式包含了兩個函式的定義:print_lyrics 和 repeat_lyrics。函式定義的語句也會被執行,但僅限於建立函式物件並不產生輸出內容,只有當函式被呼叫的時候,函式體裡面定義的語句才會被執行並輸出資料。

  因此正如你所想的那樣,在呼叫函式之前必須先進行定義,也就是說,在你呼叫函式之前,函式定義部分必須先被執行。


練習 1

  將程式的最後一行放到第一行,然後執行程式看看會得到什麼出錯資訊。

 

練習 2

  將函式呼叫的語句放回程式最後,然後把 print_lyrics 函式的定義放到 repeat_lyrics 函式的後面,然後執行程式看看會得到什麼錯誤資訊。

 

3.7 執行流程

  為了確保程式呼叫之前被定義,我們必須清楚語句執行的順序,這個就叫做執行流程。程式執行總是從第一行語句開始依次執行,並且一次執行一行。函式的定義是不會改變程式的執行流程的,請牢記函式裡面的語句只有在函式被呼叫的時候才會被執行。函式呼叫就像執行流程裡面的一條彎路,它會跳轉並執行函式裡定義的語句,結束之後再回到函式呼叫的地方接著執行下面的語句。
  你只要記住在一個函式裡面是可以呼叫其它函式的,這樣會更容易理解。當一個函式裡面呼叫另一個函式的時候,程式必須執行另一個函式裡面的語句,並且在完成之後再繼續執行這個函式的其它語句。
  幸運的是Python很擅長跟蹤,程式在呼叫函式的時候都能準確的知道呼叫的位置。當程式終止的時候,它會跳轉到程式的最後。
  這樣做有什麼寓意嗎?事實上你並非每次都是從頭開始閱讀程式程式碼,只是有時候從頭開始閱讀程式碼並遵循執行流程會讓你覺得有意義。


3.8 形參和實參

  我們發現一些內建函式是有爭議的。例如,當你呼叫math.sin函式時需要傳遞一個實參,但是像 matn.pow 函式則需要傳入兩個實參:基數和指數。
  在函式內部,實參是一個被分配給了形參的變數,這裡有一個需要傳入一個引數的使用者自定義函式:

def print_twice(bruce):
print bruce
print bruce

     這個函式把實參分配給了一個形參變數 bruce,當這個函式被呼叫的時候,它會列印兩次形參的內容(不管傳過來實參是什麼內容),並且可以列印任何可以列印的值。

>>> print_twice('Spam')
Spam
Spam
>>> print_twice(17)
17
17
>>> print_twice(math.pi)
3.14159265359
3.14159265359

     當然,適用於內建函式的規則同樣也適用於使用者自定義函式,我們可以傳遞任意表達式作為引數呼叫 print_twice 函式:

>>> print_twice('Spam '*4)
Spam Spam Spam Spam
Spam Spam Spam Spam
>>> print_twice(math.cos(math.pi))
-1.0
-1.0

     引數是在函式被呼叫之前就進行運算的,因此表示式 'Spam '*4 和 math.cos(math.pi) 是會被運算一下再傳遞給函式。

  另外,你也可以用一個變數作為引數傳遞給函式:

>>> michael = 'Eric, the half a bee.'
>>> print_twice(michael)
Eric, the half a bee.
Eric, the half a bee.

  我們把變數名 michael 作為實參傳遞給函式,這個跟函式的形參 bruce 是無關的,在函式內部(呼叫者)這個值叫什麼並不重要,在函式 print_twice 裡,它把所有傳遞過來的引數都叫 bruce。

 

3.9 變數和形參是區域性的

  當你在函式裡面定義一個變數的時候,它只能在函式內部使用,所謂區域性變數,例如:

def cat_twice(part1, part2):
cat = part1 + part2
print_twice(cat)

     這個函式有兩個引數,實現連線這兩個引數並列印兩次的功能,呼叫方式如下:

>>> line1 = 'Bing tiddle '
>>> line2 = 'tiddle bang.'
>>> cat_twice(line1, line2)
Bing tiddle tiddle bang.
Bing tiddle tiddle bang.

  當 cat_twice 函式呼叫結束,變數 cat 是會被銷燬的,如果此時你嘗試列印該變數的值,我們就會得到一個異常:

>>> print cat
NameError: name 'cat' is not defined

  事實上,函式的引數也是區域性的,例如,在 print_twice 之外是沒有哪個叫 bruce 的變數。

 

3.10 堆疊圖

  為了跟蹤變數在哪裡被使用了,因此有時候畫一個堆疊圖會很有幫助。與狀態圖類似,堆疊圖可以顯示每一個變數的值,但是它還可以顯示出每一個變數屬於哪一個函式。
  每個函式由一個方框來表示,每個方框邊上寫下函式的名字,裡面寫下函式的引數和變數。前面例子的堆疊圖如圖3.1所示。

 

圖3.1:堆疊圖


  所有的方框按序列在一個堆疊裡,並且標註其呼叫的對應的函式。在這個例子中,print_twice 被 cat_twice 呼叫,而cat_twice被__main__呼叫,__main__是最頂層的方框的特殊名稱,你在函式之外定義的所有變數都屬於__main__。
  每個引數都引用了其對應實參的值,因此,part1和line1的值相同,part2和line2的值相同,bruce的值則與cat相同。
  如果在呼叫函式的時候出錯了,Python將會列印所呼叫的函式名稱,以及呼叫該函式的函式名稱,並最終回到主函式 __main__。
  例如,如果你在 print_twice 函式訪問cat變數就會得到一個NameError:

Traceback (innermost last):
File "test.py", line 13, in __main__
cat_twice(line1, line2)
File "test.py", line 5, in cat_twice
print_twice(cat)
File "test.py", line 9, in print_twice
print cat
NameError: name 'cat' is not defined

  這個列表中的函式稱為回溯,它能告訴你哪個程式檔案出錯,出錯的行,以及正在執行哪些函式,並且能告訴你哪一行程式碼導致了錯誤。在回溯列表中的函式順序跟堆疊圖中的函式順序是一樣的,當前執行的函式在最下方。

 

3.11 有返回結果函式和無返回結果函式

  有一些我們使用的函式是有返回值的,例如數學函式。由於沒有更好的名字,我暫且稱之為有返回結果函式,像print_twice只執行一些指令的函式稱之為無返回結果函式。
  當你呼叫一個有返回值的函式,肯定是希望能利用返回結果做些什麼,例如,把返回結果賦值給一個變數或者拿它作為表示式的一部分:

x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2

  當你在互動模式下呼叫函式,Python會把結果顯示出來。

>>> math.sqrt(5)
2.2360679774997898

  但是在指令碼模式下,如果你呼叫一個有返回值函式,結果是不會顯示出來的。

math.sqrt(5)

  這個指令碼是計算5的平方根,但是並沒有儲存或顯示計算結果,因此不是很有用。

  無返回值函式可能會顯示一些內容或者產生其它影響,但是沒有返回結果,如果你嘗試把函式結果賦值給一個變數,將會得到一個特殊型別的返回值None。

>>> result = print_twice('Bing')
Bing
Bing
>>> print result
None

  這個None值跟字串‘None’是不一樣的,它是一個特定型別的特殊值:

>>> print type(None)
<type 'NoneType'>

  到目前為止,我們寫的函式都是無返回值的函式,接下來的幾章我們將開始寫一些有返回值的函式。

 

3.12 為什麼要用函式

  或許你還不明白為什麼要把一個程式拆分成函式,這裡有幾條理由:

    1. 建立新函式讓你有機會給一組程式碼進行命名,從而也讓你的程式變得更加容易閱讀和除錯。
    2. 函式可以避免重複程式碼從而使你的程式更精煉,如果處理邏輯發生變化你只需要修改一個地方就可以了。
    3. 把一個很長的程式拆分成許多函式,這樣做可以讓你一次除錯其中一個,然後再把這些函式組合成一個整體。
    4. 對很多程式來說,良好的設計是非常有用的,一旦你編寫除錯好了,你就可以進行重複利用。

 

3.13 用from匯入

  Python提供了兩種方式匯入模組,我們已經用過其中一種:

>>> import math
>>> print math
<module 'math' (built-in)>
>>> print math.pi
3.14159265359

  如果匯入了math模組,你就會得到一個名為math的模組物件,並且這個模組物件裡面已經包含了變數pi和一些數學函式,例如 sin 和 exp 等等,但是你不能這樣直接獲取 pi 的值:

>>> print pi
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'pi' is not defined

  另外還有一種import方式,你可以直接匯入模組裡面的一個物件:

>>> from math import pi

  這樣你就可以直接獲取pi值而不需要點操作。

>>> print pi
3.14159265359

  或者你可以使用星號操作符匯入模組內的所有物件:

>>> from math import *
>>> cos(pi)
-1.0

  把math模組所有物件匯入進來的好處是可以讓程式碼看起來更簡潔,但是不好的地方是在不同模組之間,或者匯入的模組裡面的變數名與你自己模組裡面的變數名可能會有衝突。


3.14 除錯

  如果你使用文字編輯器寫指令碼,可能會遇到空格或者製表符的問題。避免這個問題的最好方法就是都使用空格(不要使用製表符)。大部分Python編輯器預設情況下都是如此設定的,只有部分編輯器不是這樣。因為製表符和空格是不可見的,因此很難去進行除錯,所以儘量找一個合適的能管理程式碼縮排的編輯器。
  另外,執行程式之前記得先儲存程式碼,因為有些編輯器會自動儲存,而有些編輯器則不會。因此你需要確認執行的程式程式碼是否跟你編輯的程式碼是不是一樣的,除錯的時候如果你沒有注意到這一點,而是一遍又一遍的執行沒有儲存的程式碼,這將會浪費你很多時間。
  所以,請確保你正在執行的程式碼就是你正在檢視的程式碼,如果你不確定,就在程式碼里加一些程式碼,如 print "Hello" 放在程式碼最前面再執行,如果沒有輸出 Hello,那就證明了你運行了不正確的程式碼。

 

3.15 術語

函式:

  • 一系列命名並且完成一些操作的語句。函式可以有/沒有引數,也可以有/沒有返回值。

函式定義:

  • 一種用語句建立的新函式,併為其指定名稱,引數以及執行語句。

函式物件:

  • 由函式定義建立的一個值。函式名稱是引用函式物件的一個變數。

函式頭:

  • 函式定義的第一行。

函式體:

  • 函式定義裡面的一系列語句

形參:

  • 函式內部用來引用傳遞引數值的名稱

函式呼叫:

  • 執行函式的語句。它由函式名和引數列表組成。

實參:

  • 函式被呼叫時傳遞給函式的值。值被賦值給函式裡面對應的形參。

區域性變數:

  • 函式內部定義的變數。區域性變數只能在函式內部使用。

返回值:

  • 函式的返回結果。如果函式呼叫是使用了表示式,則返回值就是表示式的值。

有返回值函式:

  • 函式有返回結果。

無返回值函式:

  • 函式沒有返回結果。

模組:

  • 包含了相關函式和定義的集合的檔案。

匯入語句:

  • 讀取模組並且定義模組物件的語句。

模組物件:

  • 由import語句建立的值,其可以訪問對應模組裡面的值。

點語法:

  • 通過指定模組名加點(.)和函式名的方式呼叫其它模組裡面的函式。

組合:

  • 使用表示式作為一個大的表示式的一部分,或者用語句作為一個大的語句的一部分。

執行流程:

  • 程式執行時的語句執行順序。

堆疊圖:

  • 表示函式堆疊的變數和值引用的圖形。

框架:

  • 表示函式呼叫堆疊圖中的方框。它包含函式的引數和區域性變數。

回溯:

  • 發生異常時列印的正在執行的函式的列表。


3.16 練習

練習 3

  Python提供了一個可以獲取字串長度的內建函式 len,例如 len('allen')的值為5.
  請編寫一個函式right_justify ,引數名為 s,實現功能為:列印一個長度為70字串。

>>> right_justify('allen')
                                                                       allen

 

練習 4

  函式物件是可以賦值給一個變數或者作為一個引數傳遞的。例如:do_twice 是一個函式,其又一個函式物件引數,並且呼叫該函式兩次:

def do_twice(f):
f()
f()

  這裡又一個例子:利用 do_twice 呼叫 print_spam 函式兩次。

def print_spam():
print 'spam'

do_twice(print_spam)

把程式碼放入指令碼並測試。

  1. 修改 do_twice 函式,變成兩個引數,一個是函式物件,另一個是值,然後呼叫這個函式物件兩次,傳遞這個值作為引數。
  2. 編寫一個更通用的 print_spam 函式,命名為 print_twice,傳遞一個字串引數並且列印兩次。
  3. 利用 do_twice 的修改版本呼叫兩次 print_twice,傳遞一個引數 'spam' 。
  4. 定義一個新函式 do_four,接收一個函式物件引數和一個值引數,並呼叫函式物件4次,傳遞一個值引數。函式體應該只有2條語句,而不是4條語句。

答案請參考:http://thinkpython.com/code/do_four.py

 

練習 5

這個練習只能使用我們已經學過的語句來實現。
寫一個函式畫出如下網格:


+ - - - - + - - - - +
|           |           |
|           |           |
|           |           |
|           |           |
+ - - - - + - - - - +
|           |           |
|           |           |
|           |           |
|           |           |
+ - - - - + - - - - +


提示:列印多個字串可以用逗號隔開: print '+', '-'
如果列印序列以逗號結尾,則表示該行的列印內容尚未結束,接下來的列印內容會在同一行。
print '+',
print '-'
這條語句的輸出結果是 '+ -'.
每一個列印語句結束會另起一行,寫一個函式畫一個4行4列的網格。

 

參考答案:http://thinkpython.com/code/grid.py

 

#英文版權  Allen B. Downey
#翻譯中文版權  Simba Gu
#轉載請註明出處