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

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

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

 

第三章 函數


3.1 函數調用

  在程序中,函數能夠理解爲一個有名字的執行命令的序列。當你在定義一個函數的時候,必須爲其指定一個名字,而後再經過這個名字來調用。正如前一章裏面提到過的函數調用的例子:html

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

  這裏的type就是一個函數,括號裏面的表達式叫作參數,它返回傳入參數的類型。一般來講就是函數「接收」值並「返回」結果,這個結果叫作返回值。python

 

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
#轉載請註明出處
相關文章
相關標籤/搜索