廖雪峰Python教程筆記(三)
- 5 函數
5 函數
基本上所有的高級語言都支持函數,Python也不例外。Python不但能非常靈活地定義函數,而且本身內置了很多有用的函數,可以直接調用。
抽象
計算數列的和,比如:1 + 2 + 3 + … + 100
看到 ∑ 就可以理解成求和
借助抽象,我們才能不關心底層的具體計算過程,而直接在更高的層次上思考問題。
函數就是最基本的一種代碼抽象的方式。
調用函數
要調用一個函數,需要知道函數的名稱和參數,比如求絕對值的函數abs
調用函數的時候,如果傳入的參數數量不對,會報TypeError的錯誤:
abs() takes exactly one argument (2 given)
果傳入的參數數量是對的,但參數類型不能被函數所接受,也會報TypeError的錯誤:
bad operand type for abs(): ‘str’
而max函數max()可以接收任意多個參數,并返回最大的那個:
數據類型轉換
比如int()函數可以把其他數據類型轉換為整數:
函數名其實就是指向一個函數對象的引用,完全可以把函數名賦給一個變量,相當于給這個函數起了一個“別名”:
定義函數
;在Python中,定義一個函數要使用def語句,依次寫出函數名、括號、括號中的參數和冒號:然后,在縮進塊中編寫函數體,函數的返回值用return語句返回。
以自定義一個求絕對值的my_abs函數為例:
函數體內部的語句在執行時,一旦執行到return時,函數就執行完畢,并將結果返回。因此,函數內部 通過條件判斷和循環可以實現非常復雜的邏輯 。
如果沒有return語句,函數執行完畢后也會返回結果,只是結果為None。return None可以簡寫為return。
空函數
如果想定義一個什么事也不做的空函數,可以用pass語句:
pass語句什么都不做,那有什么用?實際上pass可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以
先放一個pass,讓代碼能運行起來。
參數檢查
調用函數時,如果參數個數不對,Python解釋器會自動檢查出來,并拋出TypeError:
但是如果參數類型不對,Python解釋器就無法幫我們檢查。試試my_abs和內置函數abs的差別:
傳入了不恰當的參數時,內置函數abs會檢查出參數錯誤,而我們定義的my_abs沒有參數檢查,會導致if語句出錯,出錯信息和abs不一樣。所以,這個函數定義不夠完善。
讓我們修改一下my_abs的定義,對參數類型做檢查,只允許整數和浮點數類型的參數。數據類型檢查可以用內置函數isinstance()實現:
添加了參數檢查后,如果傳入錯誤的參數類型,函數就可以拋出一個錯誤:
TypeError: bad operand type
錯誤和異常處理將在后續講到。
返回多個值
函數可以返回多個值嗎?答案是肯定的。
比如在游戲中經常需要從一個點移動到另一個點,給出坐標、位移和角度,就可以計算出新的坐標:
import math語句表示導入math包,并允許后續代碼引用math包里的sin、cos等函數。
這只是一種假象,Python函數返回的仍然是單一值:原來返回值是一個tuple
小結:
1.定義函數時,需要確定函數名和參數個數;
2.如果有必要,可以先對參數的數據類型做檢查;
3.函數體內部可以用return隨時返回函數結果;
4.函數執行完畢也沒有return語句時,自動return None。
5.函數可以同時返回多個值,但其實就是一個tuple。
**函數的參數:**除了正常定義的必選參數外,還可以使用默認參數、可變參數和關鍵字參數,使得函數定義出來的接口,不但能處理復雜的參數,還可以簡化調用者的代碼。
位置參數
一個計算x2的函數:
對于power(x)函數,參數x就是一個位置參數。
當我們調用power函數時,必須傳入有且僅有的一個參數x:
如果要計算x4、x5……怎么辦?我們不可能定義無限多個函數。
可以把power(x)修改為power(x, n),用來計算xn,說干就干:
修改后的power(x, n)函數有兩個參數:x和n,這兩個參數都是位置參數,調用函數時,傳入的兩個值按照位置順序依次賦給參數x和n。
默認參數
新的power(x, n)函數定義沒有問題,但是,舊的調用代碼失敗了,原因是我們增加了一個參數,導致舊的代碼因為缺少一個參數而無法正常調用:
TypeError: power() missing 1 required positional argument: ‘n’
這個時候,默認參數就排上用場了。由于我們經常計算x2,所以,完全可以把第二個參數n的默認值設定為2:
這樣,當我們調用power(5)時,相當于調用power(5, 2):
設置默認參數時,有幾點要注意:
一是必選參數在前,默認參數在后,否則Python的解釋器會報錯
二是如何設置默認參數。
當函數有多個參數時,把變化大的參數放前面,變化小的參數放后面。變化小的參數就可以作為默認參數。
使用默認參數有什么好處?最大的好處是能降低調用函數的難度。
一年級小學生注冊的函數,需要傳入name和gender兩個參數:
如果要繼續傳入年齡、城市等信息怎么辦?這樣會使得調用函數的復雜度大大增加。
我們可以把年齡和城市設為默認參數:
只有與默認參數不符的學生才需要提供額外的信息。
默認參數有個最大的坑,演示如下:
先定義一個函數,傳入一個list,添加一個END再返回:
再次調用add_end()時,結果就不對了:
Python函數在定義的時候,默認參數L的值就被計算出來了,即[],因為默認參數L也是一個變量,它指向對象[],每次調用該函數,如果改變了L的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]了。
無論調用多少次,都不會有問題:
可變參數
以數學題為例子,給定一組數字a,b,c……,請計算a2 + b2 + c2 + ……。
所以,我們把函數的參數改為可變參數:
定義可變參數和定義一個list或tuple參數相比,僅僅在參數前面加了一個*號。在函數內部,參數numbers接收到的是一個tuple,因此,函數代碼完全不變。
*nums表示把nums這個list的所有元素作為可變參數傳進去。這種寫法相當有用,而且很常見。
關鍵字參數
關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict。
函數person除了必選參數name和age外,還接受關鍵字參數kw。在調用該函數時,可以只傳入必選參數:
也可以傳入任意個數的關鍵字參數:
命名關鍵字參數
對于關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數。至于到底傳入了哪些,就需要在函數內部通過kw檢查。
仍以person()函數為例,我們希望檢查是否有city和job參數:
但是調用者仍可以傳入不受限制的關鍵字參數:
參數組合
在Python中定義函數,可以用必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。
比如定義一個函數,包含上述若干種參數:
函數調用:
在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。
計算階乘n! = 1 x 2 x 3 x … x n
遞歸函數的優點是定義簡單,邏輯清晰。 理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
使用遞歸函數需要注意防止棧溢出。
在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀
。由于棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出??梢栽囋噁act(1000):
RuntimeError: maximum recursion depth exceeded in comparison
解決遞歸調用棧溢出的方法是通過 尾遞歸優化 ,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。
上面的fact(n)函數由于return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:
fact(5)對應的fact_iter(5, 1)的調用如下:
小結
使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。
針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。
Python標準的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
