廖雪峯Python教程筆記(三)

廖雪峯Python教程筆記(三)

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再返回:
在這裏插入圖片描述
ZZZZZZZZZZZ
再次調用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)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。可以試試fact(1000):
RuntimeError: maximum recursion depth exceeded in comparison

解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。

上面的fact(n)函數由於return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:
在這裏插入圖片描述
fact(5)對應的fact_iter(5, 1)的調用如下:
在這裏插入圖片描述

小結

使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。
針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。
Python標準的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章