Python版本:3.6.2 操作系統:Windows 作者:SmallWZQ 截至上篇隨筆《Python數據結構之四——set(集合)》,Python基礎知識也介紹好了。接下來準備乾件“大事”。 什麼“大事”呢?下麵將要介紹Python編程的核心內容之一——函數。 對於Python編程,函數的重要 ...
Python版本:3.6.2 操作系統:Windows 作者:SmallWZQ
截至上篇隨筆《Python數據結構之四——set(集合)》,Python基礎知識也介紹好了。接下來準備乾件“大事”。
什麼“大事”呢?下麵將要介紹Python編程的核心內容之一——函數。
對於Python編程,函數的重要性不言而喻。重要的事情講三遍:函數實在是太重要,太關鍵了。
引入函數
之前,我們編寫程式遵循的原則:根據業務邏輯從上到下實現功能,其往往用一長段代碼來實現指定功能,開發過程中最常見的操作就是粘貼複製,也就是將之前實現的代碼塊複製到現需功能處。這種編程方式雖然可以應付一般性問題,但是不能對付大多數問題。這不,下麵就來個例子。
1 r1 = 12.3 2 r2 = 9.1 3 r3 = 64.21 4 s1 = 2 * 3.14 * r15 s2 = 2 * 3.14 * r26 s3 = 2 * 3.14 * r3
圓是個神奇的圖形。特別是π,它讓人類陷入無限的遐想。OK,回歸正題。為了求圓的周長,我們需要引入公式:周長 = 2 * π * r(半徑)。看到這兒,某些讀者可能會有疑惑:這跟函數有什麼關係,之前的方式依然適用。是的,這的確是可以的,但這很麻煩,太重覆啦。那如果現在需要把 π 更改為3.1415926535,那該怎麼辦呢?難道我們要一個一個地去改???Oh,my god!!!這時,我嗅到了函數的味道。
有了函數,我們就不再每次寫c = 2 * 3.14 * x
,而是寫成更有意義的函數調用c = perimeter_of_circle(x)
,而函數perimeter_of_circle本身只需要寫一次,就可以多次調用。
Python不但能非常靈活地定義函數,而且本身內置了很多有用的函數,可以直接調用。
是的,函數最大的優點:增強代碼的重用性和可讀性。Python中,函數就是最基本的一種代碼抽象的方式。
函數定義
在Python中,函數有五大要點,分別是def、函數名、函數體、參數、返回值,以及兩個英文版符號,分別是括弧(括弧內為參數)和冒號(:)。
def:函數的關鍵字,沒它可不行。
函數名:函數的名稱,根據函數名調用函數。
函數體:函數中進行一系列的具體操作。
參數:為函數體提供數據。
返回值:當函數執行完畢後,可以給調用者返回數據。
上述函數的要點中,最重要的是參數和返回值。
1.返回值
函數是一個功能塊,該功能到底執行成功與否,需要通過返回值來告知調用者。
2.參數
定義函數時,參數是一定需要考慮的。Python的函數定義非常簡單,但靈活度卻非常大。
對於函數的調用者來說,只需要知道如何傳遞正確的參數,以及函數將返回什麼樣的值就夠了,函數內部的複雜邏輯被封裝起來,調用者無需瞭解。
Python中,參數類型有:必選參數、預設參數、可變參數、關鍵字參數和命名關鍵字參數。函數中,參數定義的順序必須是:必選參數、預設參數、可變參數、命名關鍵字參數和關鍵字參數。
3.空函數
空函數:什麼事也不做,可以用pass語句。既然“一事不做”,那空函數還有什麼用處?實際上pass可以用來作為占位符,比如現在還沒想好怎麼寫函數的代碼,就可以先放一個pass,讓代碼能運行起來。如此,運行代碼程式就不會出現錯誤了。
1 #空函數 2 def nop(): 3 pass
函數參數
Python中,參數是非常靈活的。掌握參數就能領悟函數的真諦了。這是真的。參數是比較難理解的,特別是參數組合。
1.位置參數
既然說函數,就需要展示函數:
1 #位置參數(必選參數) 2 def involution(x): 3 return x * x 4 >>>involution(3) 5 9 6 >>>involution(5) 7 25
如代碼所示,參數x就是一個位置參數。
2.預設參數
Python函數支持預設參數,即可以給函數的參數指定預設值。當該參數沒有傳入相應的值時,該參數就使用預設值。
1 #預設參數 2 def involution(x,n = 2): 3 s = 1 4 while n > 0: 5 n = n - 1 6 s = s * x 7 return s 8 >>>involution(6) 9 36 10 >>>involution(5,3) 11 125
如代碼所示,當我們調用involution(5),就相當於調用involution(5,2)。
註:設置預設參數時,必選參數在前,預設參數在後,否則Python的解釋器會報錯。
3.可變參數
在Python函數中,還可以定義可變參數。顧名思義,可變參數就是傳入的參數個數是可變的,可以是1個、2個到任意個,還可以是0個。
我們以數學題為例子,給定一組數字a,b,c……,請計算a2 + b2 + c2 + ……。
要定義出這個函數,我們必須確定輸入的參數。由於參數個數不確定,我們首先想到可以把a,b,c……作為一個list或tuple傳進來,這樣,函數可以定義如下:
1 #一般性函數 2 def calc(numbers): 3 sum = 0 4 for n in numbers: 5 sum = sum + n * n 6 return sum
如何調用calc()函數呢?需要調用時,需要為參數引入list或者tuple。
1 #函數調用 2 >>> calc([1, 2, 3]) 3 14 4 >>> calc((1, 3, 5, 7)) 5 84
然而,如果我們使用可變參數,我們可以進行簡化,方法如下:
1 #可變參數 2 def calc(*numbers): 3 sum = 0 4 for n in numbers: 5 sum = sum + n * n 6 return sum
咋調用呢?這個可簡單啦,再也不用list或者tuple了。參數調用只需如下所示:
1 #可變參數的魅力 2 >>> calc(1, 2, 3) 3 14 4 >>> calc(1, 3, 5, 7) 5 84 6 7 #參數調用不用calc([1,2,3]),括弧內還用寫中括弧,好麻煩~~~
定義可變參數和定義一個list或tuple參數相比,僅僅在參數前面加了一個*
號。在函數內部,參數numbers
接收到的是一個tuple,因此,函數代碼完全不變。但是,調用該函數時,可以傳入任意個參數,包括0個參數:
1 >>> calc(1, 2) 2 5 3 >>> calc() 4 0
如果已經有一個list或者tuple,要調用一個可變參數怎麼辦?可以這樣做:
1 >>> nums = [1, 2, 3] 2 >>> calc(nums[0], nums[1], nums[2]) 3 14
這種寫法當然是可行的,問題是太繁瑣,所以Python允許你在list或tuple前面加一個*號,把list或tuple的元素變成可變參數傳進去:
1 >>> nums = [1, 2, 3] 2 >>> calc(*nums) 3 14
4.關鍵字參數
可變參數允許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple。而關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict。dict就是字典,它是鍵值對組合,益處多多~~~
1 #引入關鍵字參數,預設為**kw 2 def person(name, age, **kw): 3 print('name:', name, 'age:', age, 'other:', kw)
函數person除了必選參數name和age外,還接受關鍵字參數kw。在調用該函數時,可以只傳入必選參數(必選參數必須全部傳入,否則會出錯),也可以傳入關鍵字參數。註:關鍵字參數可是任意個的。
1 #調用關鍵字參數 2 >>>def person(name,age,**kw): 3 ... print('name:',name,'age:',age,'other:',kw) 4 ... 5 >>>person('Jack') 6 Traceback (most recent call last): 7 File "<stdin>", line 1, in <module> 8 TypeError: person() missing 1 required positional argument: 'age' 9 >>>person('Jack',36) 10 name:Jack age:36 other:{} 11 >>>person('Jack',36,city='Hangzhou') 12 name:Jack age:36 other:{'city':'Hangzhou'} 13 >>>person('Jack',36,city='Hangzhou',job='Engineer') 14 name:Jack age:36 other:{'city':'Hangzhou','job':'Engineer'}
關鍵字參數有什麼用呢?其實,既然存在就有它的強大之處。就像自然界中的萬物,物競天擇,適者生存。如果它能夠在自然界中生存下來,那麼它就有獨特的生存本領。因此,關鍵字參數還是有用武之地的。
它可以擴展函數的功能。比如,在person函數里,我們保證能接收到name和age這兩個參數,但是,如果調用者願意提供更多的參數,我們也能收到。試想你正在做一個用戶註冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足註冊的需求。
如何操作呢?我們可以先組裝出一個dict,然後,把該dict轉換為關鍵字參數傳進去:
1 >>> extra = {'city': 'Hangzhou', 'job': 'Engineer'} 2 >>> person('Jack', 36, city=extra['city'], job=extra['job']) 3 name: Jack age: 36 other: {'city': 'Hangzhou', 'job': 'Engineer'}
當然了,上面代碼調用方式有點煩,通過dict鍵來查找值。我們可以通過關鍵字簡化一下:
1 >>> extra = {'city': 'Hangzhou', 'job': 'Engineer'} 2 >>> person('Jack', 36, **extra) 3 name: Jack age: 36 other: {'city': 'Hangzhou', 'job': 'Engineer'}
**extra表示把extra這個dict的所有key-value用關鍵字參數傳入到函數的**kw參數,kw將獲得一個dict。
5.命名關鍵字參數
對於關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數。至於到底傳入了哪些,就需要在函數內部通過kw
檢查。
仍以person()函數為例,我們希望檢查是否有city和job參數:
1 def person(name, age, **kw): 2 if 'city' in kw: 3 # 有city參數 4 pass 5 if 'job' in kw: 6 # 有job參數 7 pass 8 print('name:', name, 'age:', age, 'other:', kw)
如果要限制關鍵字參數的名字,就可以用命名關鍵字參數,例如,只接收city和job作為關鍵字參數。這種方式定義的函數如下:
1 def person(name, age, *, city, job): 2 print(name, age, city, job)
和關鍵字參數*kw不同,命名關鍵字參數需要一個特殊分隔符,*後面的參數被視為命名關鍵字參數。
調用命名關鍵字參數方式如下:
1 #調用命名關鍵字參數 2 >>> person('Jack', 36, city='Hangzhou', job='Engineer') 3 Jack 36 Hangzhou Engineer
那如果參數中有可變參數,那該怎麼辦呢?
若可變參數後面跟著命名關鍵字參數,後面跟著的命名關鍵字參數就不再需要一個特殊分隔符*了。
1 def person(name, age, *args, city, job): 2 print(name, age, args, city, job)
命名關鍵字參數必須傳入參數名,這和位置參數不同。如果沒有傳入參數名,調用將報錯。而命名關鍵字參數可以有預設值,從而簡化調用:
1 def person(name, age, *, city='Hangzhou', job): 2 print(name, age, city, job)
由於命名關鍵字參數city具有預設值,調用時,可不傳入city參數:
1 >>> person('Jack', 36, job='Engineer') 2 Jack 36 Hangzhou Engineer
6.參數組合
目前,函數中共有5種常用的參數類型。若只傳入一種類型的參數,這太簡單了。難點在哪?難點就在參數組合使用,那是相當噁心。不過,平時最好不要混合使用參數,不然容易搞得“烏煙瘴氣”。
OK!言歸正傳,不然跑題啦。
Python中,定義一個函數,我們可以用必選參數、預設參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但是請註意,參數定義的順序必須是:必選參數、預設參數、可變參數、命名關鍵字參數和關鍵字參數。
下麵來定義一個函數,該函數參數包含一種或幾種參數。
1 def f1(a, b, c=0, *args, **kw): 2 print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) 3 4 def f2(a, b, c=0, *, d, **kw): 5 print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函數調用的時候,Python解釋器自動按照參數位置和參數名把對應的參數傳進去。
1 >>> f1(1, 2) 2 a = 1 b = 2 c = 0 args = () kw = {} 3 >>> f1(1, 2, c=3) 4 a = 1 b = 2 c = 3 args = () kw = {} 5 >>> f1(1, 2, 3, 'a', 'b') 6 a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} 7 >>> f1(1, 2, 3, 'a', 'b', x=99) 8 a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99} 9 >>> f2(1, 2, d=99, ext=None) 10 a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
最神奇的是通過一個tuple和dict,你也可以調用上述函數:
1 >>> args = (1, 2, 3, 4) 2 >>> kw = {'d': 99, 'x': '#'} 3 >>> f1(*args, **kw) 4 a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'} 5 >>> args = (1, 2, 3) 6 >>> kw = {'d': 88, 'x': '#'} 7 >>> f2(*args, **kw) 8 a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,對於任意函數,都可以通過類似func(*args, **kw)
的形式調用它,無論它的參數是如何定義的。
然而,雖然函數參數類型多達5種,但不要同時使用太多的組合,否則函數介面的可理解性很差。哎,簡簡單單才是真啊。
7.函數參數小結
參數,作為函數傳入值的媒介,這裡有必要做一個總結。
一、Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常複雜的參數;
二、預設參數一定要用不可變對象,如果是可變對象,程式運行時會有邏輯錯誤;
三、*args
是可變參數,args接收的是一個tuple;
四、**kw
是關鍵字參數,kw接收的是一個dict;
五、可變參數既可以直接傳入:func(1, 2, 3)
,又可以先組裝list或tuple,再通過*args
傳入:func(*(1, 2, 3))
;
六、關鍵字參數既可以直接傳入:func(a=1, b=2)
,又可以先組裝dict,再通過**kw
傳入:func(**{'a': 1, 'b': 2});
七、使用*args
和**kw
是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法;
八、命名的關鍵字參數是為了限制調用者可以傳入的參數名,同時可以提供預設值;
九、定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符*
,否則定義的將是位置參數。
故而,為了學好Python中的函數部分,參數不容忽視。
函數調用
在學習了函數的定義之後,我們應該需要調用函數,獲取我們想要的數據。
如何調用函數呢?語法:函數名(參數)
Python中,大佬們內置了許多有點用的函數,而我們只需拿來就行(這讓我想起了魯迅的“拿來主義”)。
若要調用Python中的內置函數,我們首先要知道函數名和參數。哈哈,又是參數~~~
比如我想要求某數的絕對值。如果你不知道Python有相關的內置函數,就只能這麼做:
1 #求取絕對值 2 >>>def abs(num): 3 ... if num >= 0: 4 ... return num 5 ... else: 6 ... return (-num) 7 ... 8 >>>abs(9) 9 9 10 >>>abs(0) 11 0 12 >>>abs(-8) 13 8
上述代碼雖然可以實現求絕對值的功能,但是太繁瑣,需要敲幾行代碼才能實現該功能。然而,Python中有這個函數可以直接調用並輸出結果。
1 #Python內置函數:abs() 2 >>>abs(-9) 3 9 4 >>>abs(9) 5 9 6 #獲取幫助文檔 7 >>>help(abs) 8 Help on built-in function abs in module builtins: 9 10 abs(x, /) 11 Return the absolute value of the argument.
Python官方網站:https://docs.python.org/3/library/functions.html
對於函數參數,通常會遇到以下兩個問題:
1.如果函數傳入參數的數量錯誤,會如何呢?簡單,直接Error唄。比如abs():
1 #函數傳入參數的數量錯誤 2 >>> abs(-9,89) 3 Traceback (most recent call last): 4 File "<stdin>", line 1, in <module> 5 TypeError: abs() takes exactly one argument (2 given)
2.如果傳入的參數數量是對的,但參數類型不能被函數所接受,也會報TypeError
的錯誤,並且給出錯誤信息:str
是錯誤的參數類型:
1 #傳入的參數類型錯誤 2 >>> abs('a') 3 Traceback (most recent call last): 4 File "<stdin>", line 1, in <module> 5 TypeError: bad operand type for abs(): 'str'
常見內置函數(Built-in Functions)
Python 3.x版本下官方網站:https://docs.python.org/3/library/functions.html。該網址內顯示Python內置函數相關內容(Built-in Functions)。
1.數據結構相關:list()、tuple()、dict()、str()……
2.數字相關:abs()、min()、max()、len()……
3.其他:int()、float()……
好,不一一例舉了,直接上圖吧~~~
如果讀者想知道圖中函數的詳細含義,請點擊上述鏈接網址。調皮一下,這裡就不附上網址啦~~~
數據類型轉換
Python內置的常用函數還包括數據類型轉換函數,比如int()
函數可以把其他數據類型轉換為整數:
1 #Python之數據類型轉換(int、float、str……) 2 >>> int('123') 3 123 4 >>> int(12.34) 5 12 6 >>> float('12.34') 7 12.34 8 >>> str(1.23) 9 '1.23' 10 >>> str(100) 11 '100' 12 >>> bool(1) 13 True 14 >>> bool('') 15 False
函數別名
瞭解Linux的讀者可能知道別名(alias,unalias)這個指令。Python中也有“別名”之說,比如把函數名賦給變數:
1 #函數“別名” 2 >>>abs(-8) 3 8 4 >>>a = abs 5 >>>a(-9) 6 9 7 >>>a(0) 8 0 9 >>>a(9) 10 9
遞歸函數
講述遞歸函數之前,我想起一個東西:階乘(n!)。舉個例子,我們來計算階乘n! = 1 x 2 x 3 x ... x n
,用函數fact(n)
表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
因此,遞歸函數:在函數內部,一個函數在內部調用自身本身。
於是,fact(n)用遞歸的方式寫出來就是:
1 #遞歸函數 2 >>>def fact(n): 3 ... if n == 1: 4 ... return 1 5 ... else: 6 ... return fact(n - 1) * n 7 ... 8 >>>fact(1) 9 1 10 >>>fact(5) 11 120 12 #遞歸函數之棧溢出 13 >>>fact(1000) 14 Traceback (most recent call last): 15 File "<stdin>", line 1, in <module> 16 File "<stdin>", line 5, in x 17 File "<stdin>", line 5, in x 18 File "<stdin>", line 5, in x 19 [Previous line repeated 994 more times] 20 File "<stdin>", line 2, in x 21 RecursionError: maximum recursion depth exceeded in comparison
如代碼所示,使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。
針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和迴圈是等價的,沒有迴圈語句的編程語言只能通過尾遞歸實現迴圈。
Python標準的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。
什麼是尾遞歸?這個請讀者自行查詢唄,這裡就不介紹啦,嘿嘿~~~
下麵來個斐波拉契數列:
1 #斐波拉契數列 2 >>>def fibo(arg1,arg2): 3 ... if arg1 == 0: 4 ... print(arg1,arg2) 5 ... arg3 = arg1 + arg2 6 ... print(arg3) 7 ... fibo(arg2, arg3) 8 ... 9 >>>fibo(0,1)
上述代碼展示的斐波拉契數列會一直計算,直至棧溢出:
1 #斐波拉契數列導致棧溢出 2 488272859468887457959087733119242564077850743657661180827326798539177758919828135114407499369796465649524266755391104990099120377 3 Traceback (most recent call last): 4 File "<stdin>", line 1, in <module> 5 File "<stdin>", line 6, in fibo 6 File "<stdin>", line 6, in fibo 7 File "<stdin>", line 6, in fibo 8 [Previous line repeated 992 more times] 9 File "<stdin>", line 5, in fibo 10 RecursionError: maximum recursion depth exceeded while calling a Python object 11 16602747662452097049541800472897701834948051198384828062358553091918573717701170201065510185595898605104094736918879278462233015981029522997836311232618760539199036765399799926731433239718860373345088375054249
如何才能避免棧溢出呢?自己想唄,要不大腦會生鏽的。
對於漢羅塔問題,利用遞歸來解決該問題也是相當的簡單,且代碼清晰:
1 #遞歸解決漢羅塔問題 2 >>>def hanrota(n,a,b,c): 3 ... if n == 1: 4 ... print(a,'-->',c) 5 ... else: 6 ... hanrota(n - 1,a,c,b) 7 ... hanrota(1,a,b,c) 8 ... hanrota(n - 1,b,a,c) 9 ... 10 >>>hanrota(3,'A','B','C') 11 A --> C 12 A --> B 13 C --> B 14 A --> C 15 B --> A 16 B --> C 17 A --> C漢羅塔問題
匿名函數
定義函數真得不可多得,但有時候不需要顯示地定義函數。因此,函數也需要靈活地運用。使用匿名函數可以更加方便。
匿名函數語法:
lambda x: x * x(關鍵字lambda
表示匿名函數,冒號前面的x
表示函數參數)
1 def f(x): 2 return x * x
匿名函數的好處:因為函數沒有名字,不必擔心函數名衝突。
1 def is_odd(n): 2 return n % 2==1 3 4 L = list(filter(lambda n: n%2==1,range(1,20))) 5 print(L)
1.匿名函數也是一個函數對象,也可以把匿名函數賦值給一個變數,再利用變數來調用該函數。
2.Python中,匿名函數可以作為返回值返回並輸出結果。