Python之--自定義函數:
在Python中,定義一個函數要使用def
語句,依次寫出函數名、括號、括號中的參數和冒號:
,然后,在縮進塊中編寫函數體,函數的返回值用return
語句返回。
以下自定義一個函數用於判斷一個人是成年人好事青少年:
1 >>> def judge_person(age): 2 ... if age < 18: 3 ... print("teenager!") 4 ... else: 5 ... print("adult!") 6 ... 7 >>> judge_person(12) 8 teenager! 9 >>> judge_person(23) 10 adult! 11 >>>
請注意,函數體內部的語句在執行時,一旦執行到return
時,函數就執行完畢,並將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
如果沒有return
語句,函數執行完畢后也會返回結果,只是結果為None
。
return None
可以簡寫為return
1 >>> def get_age(age): 2 ... if age > 18: 3 ... return "older" 4 ... else: 5 ... return 6 ... 7 >>> get_age(12) 8 >>> get_age(56) 9 'older' 10 >>>
空函數:
什么叫做”空函數“?所謂的空函數就是你定義一個函數,但是什么也不用做,就是定義了一個空函數!例如:
1 >>> def Empty(): 2 ... pass 3 ... 4 >>> def Empty2(): 5 ... pass 6 ... 7 >>>
pass
語句什么都不做,那有什么用?實際上pass
可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以先放一個pass
,讓代碼能運行起來。
pass
還可以用在其他語句里,比如:
1 >>> age = 90 2 >>> if age: 3 ... pass 4 ... 5 >>> #如果沒有pass就會報錯。 6 ... 7 >>> if age: 8 ... 9 File "<stdin>", line 2 10 11 ^ 12 IndentationError: expected an indented block 13 >>>
調用函數時,如果參數個數不對,Python解釋器會自動檢查出來,並拋出TypeError
:
但是如果參數類型不對,Python解釋器就無法幫我們檢查。
當傳入了不恰當的參數時,內置函數abs
會檢查出參數錯誤,而我們定義沒有參數檢查,所以,這個函數定義不夠完善。
以下定義一個求絕對值的函數:
1 >>> def get_abs(value): 2 ... if value < 0: 3 ... return -value 4 ... else: 5 ... return value 6 7 >>> get_abs(2) 8 2 9 >>> get_abs(-3) 10 3 11 >>> get_abs(0) 12 0 13 >>>
讓我們修改一下我們函數
定義get_abs,對參數類型做檢查,只允許整數和浮點數類型的參數。數據類型檢查可以用內置函數isinstance
實現:
1 >>> get_abs(dada) 2 Traceback (most recent call last): 3 File "<stdin>", line 1, in <module> 4 NameError: name 'dada' is not defined 5 >>> #在沒有進行參數檢查之前,報出了一個NameError:的錯誤 6 ... #以下我們對函數進行參數檢查,提高函數的健壯性。 7 ... 8 >>> def get_abc(value): 9 ... if not isinstance(value,(int,float)): 10 ... raise TypeError('參數類型不對,不好意思啊!') 11 ... if value >= 0: 12 ... return value 13 ... else: 14 ... return -value 15 ... 16 >>> #傳入正確的參數: 17 ... 18 >>> get_abc(12) 19 12 20 >>> get_abc(-12) 21 12 22 >>> #傳入錯誤的參數: 23 >>> get_abc('gn') 24 Traceback (most recent call last): 25 File "<stdin>", line 1, in <module> 26 File "<stdin>", line 3, in get_abc 27 TypeError: 參數類型不對,不好意思啊! 28 >>>
返回多個值:
比如在游戲中經常需要從一個點移動到另一個點,給出坐標、位移和角度,就可以計算出新的新的坐標:
1 import math 2 3 def move(x, y, step, angle=0): 4 nx = x + step * math.cos(angle) 5 ny = y - step * math.sin(angle) 6 return nx, ny
例子:
1 >>> import math 2 >>> 3 >>> def move(x,y,step,angle = 0): 4 ... nx = x + step*math.cos(angle) 5 ... ny = y - step*math.sin(angle) 6 ... return nx,ny 7 ... 8 >>> x1,y1 = move(100,10,60,math.pi/6) 9 >>> print x1,y1 10 151.961524227 -20.0 11 >>>
其實,返回的值是一個元組(tuple)。
函數的參數:
定義函數的時候,我們把參數的名字和位置確定下來,函數的接口定義就完成了。對於函數的調用者來說,只需要知道如何傳遞正確的參數,以及函數將返回什么樣的值就夠了,函數內部的復雜邏輯被封裝起來,調用者無需了解。
Python的函數定義非常簡單,但靈活度卻非常大。除了正常定義的必選參數外,還可以使用默認參數、可變參數和關鍵字參數,使得函數定義出來的接口,不但能處理復雜的參數,還可以簡化調用者的代碼。
1.默認參數:
定義一個計算平方的函數:power()
1 >>> def power(x): 2 ... return x*x 3 ... 4 >>> power(5) 5 25 6 >>> power(7) 7 49 8 >>>
當我們調用power
函數時,必須傳入有且僅有的一個參數x.但是此時我們要定義一個計算x的三次方甚至更多的次方該怎么辦呢,或許聰明的讀者已經想到了把power(x)修改為:power(x,n)實例如下:
1 >>> def power(x,n): 2 ... s = 1 3 ... while n > 0: 4 ... n = n-1 5 ... s = s * x 6 ... print s 7 ... 8 >>> power(2,2) 9 4 10 >>> power(2,9) 11 512 12 >>> #但是此時想要使用power(x)求x的平方的話,該方法已經沒有效果了。 13 ... 14 >>> power(5) 15 Traceback (most recent call last): 16 File "<stdin>", line 1, in <module> 17 TypeError: power() takes exactly 2 arguments (1 given) 18 >>> #此處我們可以引入默認參數來解決這個問題: 19 ... 20 >>> def power(x,n = 2): 21 ... s = 1 22 ... while n > 0: 23 ... n -= 1 24 ... s *= x 25 ... print s 26 ... 27 >>> power(5,2) 28 25 29 >>> power(5) 30 25 31 >>> #這樣就可以實現效果了!是不是很帥!
而對於n > 2
的其他情況,就必須明確地傳入n,比如power(5, 3)
。
從上面的例子可以看出,默認參數可以簡化函數的調用。設置默認參數時,有幾點要注意:
一是必選參數在前,默認參數在后,否則Python的解釋器會報錯(思考一下為什么默認參數不能放在必選參數前面);
二是如何設置默認參數。
當函數有多個參數時,把變化大的參數放前面,變化小的參數放后面。變化小的參數就可以作為默認參數。
使用默認參數有什么好處?最大的好處是能降低調用函數的難度。
可見,默認參數降低了函數調用的難度,而一旦需要更復雜的調用時,又可以傳遞更多的參數來實現。無論是簡單調用還是復雜調用,函數只需要定義一個。
默認參數雖然可以帶來很大的便利,但是也有一個相當大的弊端:
先定義一個函數,傳入一個list,添加一個END
再返回:
1 >>> def add_end(L=[]): 2 ... L.append(['END']) 3 ... return L 4 ... 5 >>> add_end([1,2,3]) 6 [1, 2, 3, ['END']] 7 >>> add_end(['x','y','z']) 8 ['x', 'y', 'z', ['END']] 9 >>> #當你使用默認參數的時候,剛開始是對的: 10 ... 11 >>> add_end() 12 [['END']] 13 >>> #但是再次調用add_end()時,結果就不對了 14 ... 15 >>> add_end() 16 [['END'], ['END']] 17 >>> add_end() 18 [['END'], ['END'], ['END']] 19 >>>
默認參數是[]
,但是函數似乎每次都“記住了”上次添加了'END'
后的list。這是為什么呢?
Python函數在定義的時候,默認參數L
的值就被計算出來了,即[]
,因為默認參數L
也是一個變量,它指向對象[]
,每次調用該函數,如果改變了L
的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]
了。
所以,定義默認參數要牢記一點:默認參數必須指向不變對象!
要修改上面的例子,我們可以用None
這個不變對象來實現:
1 >>> def add_end(L = None): 2 ... if L is None: 3 ... L = [] 4 ... L.append('END') 5 ... return L 6 ... 7 >>> #演示: 8 ... 9 >>> add_end() 10 ['END'] 11 >>> add_end() 12 ['END'] 13 >>> add_end() 14 ['END'] 15 >>>
為什么要設計str、None這樣的不變對象呢?因為不變對象一旦創建,對象內部的數據就不能修改,這樣就減少了由於修改數據導致的錯誤。此外,由於對象 不變,多任務環境下同時讀取對象不需要加鎖,同時讀一點問題都沒有。我們在編寫程序時,如果可以設計一個不變對象,那就盡量設計成不變對象。
2. 可變參數:
在Python函數中可以傳入默認參數,但是也可以傳入可變參數.不難理解,可變參數的含義就是說,參數的個數時可以改變的,可以是1個、2個到任意個,還可以是0個。
我們以數學題為例子,給定一組數字a,b,c……,請計算a2 + b2 + c2 + ……。
要定義出這個函數,我們必須確定輸入的參數。由於參數個數不確定,我們首先想到可以把a,b,c……作為一個list或tuple傳進來,這樣,函數可以定義如下:
在沒有使用可變參數的情況:
1 >>> def get_value(numbers): 2 ... sum = 0 3 ... for n in numbers: 4 ... sum += n * n 5 ... return sum 6 ... 7 >>> #但是調用的時候要先拼接一個元組或者列表 8 ... 9 >>> get_value([1,2,3,4,5,6,7,8,9,10]) 10 385 11 >>> get_value((1,2,3,3,4,5,6)) 12 100
使用可變參數,把函數修改為如下:
1 >>> def get_value(*numbers): 2 ... sum = 0 3 ... for n in numbers: 4 ... sum += n*n 5 ... return sum 6 ... 7 >>> get_value(1,2,3,4,5,6) 8 91
定義可變參數和定義list或tuple參數相比,僅僅在參數前面加了一個*
號。在函數內部,參數numbers
接收到的是一個tuple,因此,函數代碼完全不變。但是,調用該函數時,可以傳入任意個參數,包括0個參數:
1 >>> get_value(1) 2 1 3 >>> get_value(0) 4 0
但是,當你傳入一個列表或者元組會出現錯誤:
1 >>> get_value([1,2,3,4,5,6]) 2 Traceback (most recent call last): 3 File "<stdin>", line 1, in <module> 4 File "<stdin>", line 4, in get_value 5 TypeError: can't multiply sequence by non-int of type 'list' 6 >>> get_value((1,2,3,4,5,6)) 7 Traceback (most recent call last): 8 File "<stdin>", line 1, in <module> 9 File "<stdin>", line 4, in get_value 10 TypeError: can't multiply sequence by non-int of type 'tuple' 11 >>>
可以這樣解決列表和元組的問題:
1 >>> list1 = [8,9] 2 >>> tuple1 = (1,2,3) 3 >>> list1 4 [8, 9] 5 >>> tuple1 6 (1, 2, 3) 7 >>> get_value(list1[0],list1[1]) 8 145 9 >>> get_value(tuple1[0],tuple1[1],tuple1[2]) 10 14 11 >>>
但是,這種方法不是最好的,假如元組或者列表很大的時候就會出現工作量的問題,因此有一個更好的辦法就是:Python允許你在list或tuple前面加一個*
號,把list或tuple的元素變成可變參數傳進去:
1 >>> list1 2 [8, 9] 3 >>> tuple1 4 (1, 2, 3) 5 >>> get_value(*tuple1) 6 14 7 >>> get_value(*list1) 8 145 9 >>>
有木有感覺python真的是無可挑剔啊,確實是這樣的。
3. 關鍵字參數:
可變參數允許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple。而關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict。請看示例:
1 >>> def person(name,age,**kw): 2 ... print 'name:',name,'age:',age,'other:',kw 3 ... 4 >>> #函數person除了必選參數name和age外,還接受關鍵字參數kw。在調用該函數時,可以只傳入必選參數: 5 ... 6 >>> person('lt',20) 7 name: lt age: 20 other: {} 8 >>>
也可以傳入任意個關鍵字參數:
1 >>> person('lt',20,city = 'shanghai',height = 178,love = 'lt') 2 name: lt age: 20 other: {'city': 'shanghai', 'love': 'lt', 'height': 178} 3 >>> person('lt',20,city = 'shanghai',height = 178,love = 'lt',salary = 200000) 4 name: lt age: 20 other: {'salary': 200000, 'city': 'shanghai', 'love': 'lt', 'height': 178} 5 >>>
關鍵字參數有什么用?它可以擴展函數的功能。比如,在person
函數里,我們保證能接收到name
和age
這兩個參數,但是,如果調用者願意提供更多的參數,我們也能收到。試想你正在做一個用戶注冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足注冊的需求。
和可變參數類似,也可以先組裝出一個dict,然后,把該dict轉換為關鍵字參數傳進去:
1 >>> kw = {'name':'lt','city':'shanghai','hometown':'xinnong','age':20} 2 >>> kw 3 {'city': 'shanghai', 'age': 20, 'name': 'lt', 'hometown': 'xinnong'} 4 >>> person('jack',20,city=kw['city'],hometown = kw['hometown']) 5 name: jack age: 20 other: {'city': 'shanghai', 'hometown': 'xinnong'}
但是,更簡單便捷的方法時這樣的:
1 >>> kw = {'city':'shanghai','hometown':'xingnong'} 2 >>> kw 3 {'city': 'shanghai', 'hometown': 'xingnong'} 4 >>> person('Jack',12,**kw) 5 name: Jack age: 12 other: {'city': 'shanghai', 'hometown': 'xingnong'} 6 >>>
4.參數組合:
在Python中定義函數,可以用必選參數、默認參數、可變參數和關鍵字參數,這4種參數都可以一起使用,或者只用其中某些,但是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數和關鍵字參數。
比如定義一個函數,包含上述4種參數:
1 >>> def func(a,b,c=0,*args,**kw): 2 ... print 'a=',a,'b=',b,'c=',c,'args = ',args,'kw = ',kw 3 ... 4 >>> func(1,2) 5 a= 1 b= 2 c= 0 args = () kw = {}
1 >>> func(1,2,c=3) 2 a= 1 b= 2 c= 3 args = () kw = {} 3 >>> func(1,2,3,'a','b') 4 a= 1 b= 2 c= 3 args = ('a', 'b') kw = {} 5 >>> func(1,2,3,'a','b',x = 45,y = 65,z = 32) 6 a= 1 b= 2 c= 3 args = ('a', 'b') kw = {'y': 65, 'x': 45, 'z': 32} 7 >>>
最神奇的是通過一個tuple和dict,你也可以調用該函數:
1 >>> args = (1, 2, 3, 4) 2 >>> kw = {'x': 99} 3 >>> func(*args, **kw) 4 a = 1 b = 2 c = 3 args = (4,) kw = {'x': 99}
所以,對於任意函數,都可以通過類似func(*args, **kw)
的形式調用它,無論它的參數是如何定義的。