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

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