函數(shù)就是一段封裝好的,可以重復(fù)使用的代碼,它使得我們的程序更加模塊化,不需要編寫大量重復(fù)的代碼。函數(shù)能提高應(yīng)用的模塊性,和代碼的重復(fù)利用率。Python
提供了許多內(nèi)建函數(shù),比如print()
。但我們也可以自己創(chuàng)建函數(shù),這被叫做用戶自定義函數(shù)。
在Python
中可以使用def
關(guān)鍵字來定義函數(shù),和變量一樣每個函數(shù)也有一個響亮的名字,而且命名規(guī)則跟變量的命名規(guī)則是一致的,但不建議讀者使用a、b、c
這類簡單的標識符作為函數(shù)名,函數(shù)名最好能夠體現(xiàn)出該函數(shù)的功能(如上面的my_len
,即表示我們自定義的len()
函數(shù))。在函數(shù)名后面的圓括號中可以放置傳遞給函數(shù)的參數(shù),這一點和數(shù)學(xué)上的函數(shù)非常相似,程序中函數(shù)的參數(shù)就相當于是數(shù)學(xué)上說的函數(shù)的自變量,而函數(shù)執(zhí)行完成后我們可以通過return關(guān)鍵字來返回一個值,這相當于數(shù)學(xué)上說的函數(shù)的因變量。具體規(guī)則如下:
def
關(guān)鍵詞開頭,后接函數(shù)標識符名稱和圓括號()
。return
[表達式] 結(jié)束函數(shù),選擇性地返回一個值給調(diào)用方,不帶表達式的 return 相當于返回 None。具體語法格式如下:
def 函數(shù)名(參數(shù)列表):
# 函數(shù)體
[return [返回值]] # [] 括起來的為可選擇部分,即可以使用,也可以省略
溫馨提示: 在創(chuàng)建函數(shù)時,即使函數(shù)不需要參數(shù),也必須保留一對空的
“()”
,否則Python
解釋器將提示“invaild syntax”
錯誤。另外,如果想定義一個沒有任何功能的空函數(shù),可以使用pass
語句作為占位符。
要調(diào)用一個函數(shù),需要知道函數(shù)的名稱和參數(shù),如果傳入的參數(shù)數(shù)量不對,會報TypeError
的錯誤;如果傳入的參數(shù)數(shù)量是對的,但參數(shù)類型不能被函數(shù)所接受,也會報TypeError
的錯誤。
在Python
中,函數(shù)的參數(shù)可以有默認值,也支持使用可變參數(shù),所以Python
并不需要像其他語言一樣支持函數(shù)的重載,因為我們在定義一個函數(shù)的時候可以讓它有多種不同的使用方式,下面是兩個小例子。
from random import randint
def roll_dice(n=2):
"""搖色子"""
total = 0
for _ in range(n):
total += randint(1, 6)
return total
def add(a=0, b=0, c=0):
"""三個數(shù)相加"""
return a + b + c
# 如果沒有指定參數(shù)那么使用默認值搖兩顆色子
print(roll_dice())
# 搖三顆色子
print(roll_dice(3))
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
# 傳遞參數(shù)時可以不按照設(shè)定的順序進行傳遞
print(add(c=50, a=100, b=200))
上面的例子中兩個函數(shù)的參數(shù)都設(shè)定了默認值,這也就意味著如果在調(diào)用函數(shù)的時候如果沒有傳入對應(yīng)參數(shù)的值時將使用該參數(shù)的默認值,所以在上面的代碼中我們可以用各種不同的方式去調(diào)用add
函數(shù),這跟其他很多語言中函數(shù)重載的效果是一致的。
其實上面的add
函數(shù)還有更好的實現(xiàn)方案,因為我們可能會對0個或多個參數(shù)進行加法運算,而具體有多少個參數(shù)是由調(diào)用者來決定,我們作為函數(shù)的設(shè)計者對這一點是一無所知的,因此在不確定參數(shù)個數(shù)的時候,我們可以使用可變參數(shù),代碼如下所示。
# 在參數(shù)名前面的*表示args是一個可變參數(shù)
def add(*args):
total = 0
for val in args:
total += val
return total
# 在調(diào)用add函數(shù)時可以傳入0個或多個參數(shù)
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))
定義函數(shù)時都會選擇有參數(shù)的函數(shù)形式,函數(shù)參數(shù)的作用是傳遞數(shù)據(jù)給函數(shù),令其對接收的數(shù)據(jù)做具體的操作處理。
在使用函數(shù)時,經(jīng)常會用到形式參數(shù)(簡稱“形參”)和實際參數(shù)(簡稱“實參”),二者都叫參數(shù),之間的區(qū)別是:
Python
中,根據(jù)實際參數(shù)的類型不同,參數(shù)傳遞分為兩種情況:
C++
的值傳遞,如整數(shù)、字符串、元組。如fun(a)
,傳遞的只是a
的值,沒有影響a
對象本身。如果在fun(a)
內(nèi)部修改a
的值,則是新生成一個a
的對象。C++
的引用傳遞,如 列表,字典。如fun(la)
,則是將la
真正的傳過去,修改后fun
外部的la
也會受影響。在進行值傳遞時,改變形式參數(shù)的值后,實際參數(shù)的值不改變;在進行引用傳遞時,改變形式參數(shù)的值后,實際參數(shù)的值也發(fā)生改變。具體了解,請參考:Python函數(shù)參數(shù)傳遞機制
調(diào)用函數(shù)時可使用的正式參數(shù)類型:位置參數(shù)、關(guān)鍵字參數(shù)、默認參數(shù)、不定長參數(shù)。
位置參數(shù),有時也稱必需參數(shù),指的是必須按照正確的順序?qū)嶋H參數(shù)傳到函數(shù)中,換句話說,調(diào)用函數(shù)時傳入實際參數(shù)的數(shù)量和位置都必須和定義函數(shù)時保持一致。
1. 實參和形參數(shù)量必須一致
在調(diào)用函數(shù),指定的實際參數(shù)的數(shù)量,必須和形式參數(shù)的數(shù)量一致(傳多傳少都不行),否則Python
解釋器會拋出 TypeError
異常,并提示缺少必要的位置參數(shù)。
def girth(width, height):
return 2 * (width + height)
# 調(diào)用函數(shù)時,必須傳遞2個參數(shù),否則會引發(fā)錯誤
print(girth(3, 3))
2. 實參和形參位置必須一致
在調(diào)用函數(shù)時,傳入實際參數(shù)的位置必須和形式參數(shù)位置一一對應(yīng),否則會產(chǎn)生以下 2 種結(jié)果:
(1)拋出TypeError
異常
當實際參數(shù)類型和形式參數(shù)類型不一致,并且在函數(shù)中,這兩種類型之間不能正常轉(zhuǎn)換,此時就會拋出TypeError
異常。
def area(height,width):
return height*width/2
print(area("C語言中文網(wǎng)",3))
(2)產(chǎn)生的結(jié)果和預(yù)期不符
調(diào)用函數(shù)時,如果指定的實際參數(shù)和形式參數(shù)的位置不一致,但它們的數(shù)據(jù)類型相同,那么程序?qū)⒉粫伋霎惓?,只不過導(dǎo)致運行結(jié)果和預(yù)期不符。
# 計一個求梯形面積的函數(shù),并利用此函數(shù)求上底為 4cm,下底為 3cm,高為 5cm 的梯形的面積。
# 但如果交互高和下低參數(shù)的傳入位置,計算結(jié)果將導(dǎo)致錯誤
def area(upper_base,lower_bottom,height):
return (upper_base+lower_bottom)*height/2
print("正確結(jié)果為:",area(4,3,5)) # 17.5
print("錯誤結(jié)果為:",area(4,5,3)) # 13.5
關(guān)鍵字參數(shù)是指使用形式參數(shù)的名字來確定輸入的參數(shù)值。通過此方式指定函數(shù)實參時,不再需要與形參的位置完全一致,只要將參數(shù)名寫正確即可。
def dis_str(str1,str2):
print("str1:",str1)
print("str2:",str2)
dis_str('Python', 'Java') # 位置參數(shù)
dis_str(str1 = 'Python', str2 = 'Java') # 關(guān)鍵字參數(shù)
dis_str('Python', str2 = 'Java') # 混合參數(shù)
在調(diào)用有參函數(shù)時,既可以根據(jù)位置參數(shù)來調(diào)用,也可以使用關(guān)鍵字參數(shù)來調(diào)用。在使用關(guān)鍵字參數(shù)調(diào)用時,可以任意調(diào)換參數(shù)傳參的位置。但需要注意,混合傳參時關(guān)鍵字參數(shù)必須位于所有的位置參數(shù)之后。
Python
允許為參數(shù)設(shè)置默認值,即在定義函數(shù)時,直接給形式參數(shù)指定一個默認值。這樣的話,即便調(diào)用函數(shù)時沒有給擁有默認值的形參傳遞參數(shù),該參數(shù)可以直接使用定義函數(shù)時設(shè)置的默認值。
Python
定義帶有默認值參數(shù)的函數(shù),其語法格式如下:
def 函數(shù)名(...,形參名,形參名=默認值):
代碼塊
==注意,在使用此格式定義函數(shù)時,指定有默認值的形式參數(shù)必須在所有沒默認值參數(shù)的最后,否則會產(chǎn)生語法錯誤。==在Python
中,可以使用函數(shù)名.__defaults__
查看函數(shù)的默認值參數(shù)的當前值,其結(jié)果是一個元組。
使用可變對象作為參數(shù)默認值,多次調(diào)用可能導(dǎo)致意外情況。如下面的例子:
def demo(obj=[]):
print('obj的值:', obj)
obj.append(1)
demo() # 第一次調(diào)用,輸出[]
demo() # 第二次調(diào)用,輸出[1]
從上面的結(jié)果看,這顯然不是我們想要的結(jié)果。為了防止出現(xiàn)這種情況,最好使用None
作為可變對象的默認值,這時還需要加上必要的檢查代碼。修改后的代碼如下:
def demo(obj=None):
if obj == None:
obj = []
print('obj的值:', obj)
obj.append(1)
定義函數(shù)時,為形式參數(shù)設(shè)置默認值要牢記一點:默認參數(shù)必須指向不可變對象。
在Python
函數(shù)中,還可以定義可變參數(shù)。顧名思義,可變參數(shù)就是傳入的參數(shù)個數(shù)是可變的,可以是1個、2個到任意個,還可以是0個。
加了星號*
的參數(shù)會以元組(tuple
)的形式導(dǎo)入,存放所有未命名的變量參數(shù)。
def printinfo( arg1, *vartuple ):
"""打印任何傳入的參數(shù)"""
print ("輸出: ")
print (arg1)
print (vartuple)
# 調(diào)用printinfo 函數(shù)
printinfo( 70, 60, 50 ) # 70 (60, 50)
加了兩個星號**
的參數(shù)會以字典的形式導(dǎo)入。
def printinfo( arg1, **vardict ):
"打印任何傳入的參數(shù)"
print ("輸出: ")
print (arg1)
print (vardict)
# 調(diào)用printinfo 函數(shù)
printinfo(1, name='小明', 數(shù)學(xué)=90) # 1 {'name': '小明', '數(shù)學(xué)': 90}
聲明函數(shù)時,參數(shù)中星號*
以單獨出現(xiàn),如果單獨出現(xiàn)星號*
后的參數(shù)必須用關(guān)鍵字傳入。
def f(a, b, *, c):
return a + b + c
f(1, 2, 3) # 報錯
f(1, 2, c=3) # 6
如果想要使用一個已經(jīng)存在的列表作為函數(shù)的可變參數(shù),可以在列表的名稱前加
*
。如果想要使用一個已經(jīng)存在的字典作為函數(shù)的可變參數(shù),可在字典的名稱前加**
。
def printsign(**sign):
print()
for key, value in sign.items():
print('[' + key + ']的星座是:' + value)
printsign(憶夢='水瓶座', 香凝='雙子座')
dict1 = {'憶夢': '水瓶座', '香凝': '雙子座'}
printsign(**dict1)
Python
中,用def
語句創(chuàng)建函數(shù)時,可以用return
語句指定應(yīng)該返回的值,該返回值可以是任意類型,作用是將函數(shù)的處理結(jié)果返回給調(diào)用它的程序。需要注意的是,return
語句在同一函數(shù)中可以出現(xiàn)多次,但只要有一個得到執(zhí)行,就會直接結(jié)束函數(shù)的執(zhí)行。如果同時返回多個值,則返回的是一個元組,當函數(shù)中沒有return
語句時,或者省略了return
語句的參數(shù)時,將返回None
,即返回空值。
所謂作用域(Scope),就是變量的有效范圍,就是變量可以在哪個范圍以內(nèi)使用。有些變量可以在整段代碼的任意位置使用,有些變量只能在函數(shù)內(nèi)部使用,有些變量只能在for
循環(huán)內(nèi)部使用。
1. 局部變量
在函數(shù)內(nèi)部定義的變量,它的作用域也僅限于函數(shù)內(nèi)部,出了函數(shù)就不能使用了,我們將這樣的變量稱為局部變量(Local Variable)。
2. 全局變量
除了在函數(shù)內(nèi)部定義變量,Python
還允許在所有函數(shù)的外部定義變量,這樣的變量稱為全局變量(Global Variable)。和局部變量不同,全局變量的默認作用域是整個程序,即全局變量既可以在各個函數(shù)的外部使用,也可以在各函數(shù)內(nèi)部使用。
定義全局變量的方式有以下 2 種:
global
關(guān)鍵字對變量進行修飾后,該變量就會變?yōu)槿肿兞俊?/li>獲取指定作用域范圍中的變量
globals()
函數(shù)為Python
的內(nèi)置函數(shù),它可以返回一個包含全局范圍內(nèi)所有變量的字典,該字典中的每個鍵值對,鍵為變量名,值為該變量的值。locals()
函數(shù)也是Python
內(nèi)置函數(shù)之一,通過調(diào)用該函數(shù),我們可以得到一個包含當前作用域內(nèi)所有變量的字典。這里所謂的“當前作用域”指的是,在函數(shù)內(nèi)部調(diào)用locals()
函數(shù),會獲得包含所有局部變量的字典;而在全局范文內(nèi)調(diào)用locals()
函數(shù),其功能和globals()
函數(shù)相同。vars()
函數(shù)也是Python
內(nèi)置函數(shù),其功能是返回一個指定object
對象范圍內(nèi)所有變量組成的字典。如果不傳入object
參數(shù),vars()
和 locals()
的作用完全相同。3. 局部函數(shù)
Python
支持在函數(shù)內(nèi)部定義函數(shù),此類函數(shù)又稱為局部函數(shù)。
#全局函數(shù)
def outdef ():
#局部函數(shù)
def indef():
print("I like programing")
#調(diào)用局部函數(shù)
indef()
#調(diào)用全局函數(shù)
outdef()
注意:盡管
Python
允許全局變量和局部變量重名,但是在實際開發(fā)時,不建議這么做,因為這樣容易讓代碼混亂,很難分清哪些是全局變量,哪些是局部變量。
函數(shù)式編程就是一種抽象程度很高的編程范式,純粹的函數(shù)式編程語言編寫的函數(shù)沒有變量,因此,任意一個函數(shù),只要輸入是確定的,輸出就是確定的,這種純函數(shù)我們稱之為沒有副作用。而允許使用變量的程序設(shè)計語言,由于函數(shù)內(nèi)部的變量狀態(tài)不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數(shù)是有副作用的。
函數(shù)式編程的一個特點就是,允許把函數(shù)本身作為參數(shù)傳入另一個函數(shù),還允許返回一個函數(shù)!
Python
對函數(shù)式編程提供部分支持。由于Python
允許使用變量,因此,Python
不是純函數(shù)式編程語言。
接受函數(shù)為參數(shù),或者把函數(shù)作為結(jié)果返回的函數(shù)是高階函數(shù)(higher-order function)。
1. 變量可以指向函數(shù)
一個變量指向了一個函數(shù),那么可以通過該變量來調(diào)用這個函數(shù)。
>>> f = abs
>>> f(-10)
10
2. 函數(shù)名也是變量
函數(shù)名其實就是指向函數(shù)的變量,對于abs()這個函數(shù),完全可以把函數(shù)名abs
看成變量,它指向一個可以計算絕對值的函數(shù)!
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs
指向10后,就無法通過abs(-10)
調(diào)用該函數(shù)了!因為abs
這個變量已經(jīng)不指向求絕對值函數(shù)而是指向一個整數(shù)10!要恢復(fù)abs
函數(shù),請重啟Python
交互環(huán)境。
注:由于
abs
函數(shù)實際上是定義在import builtins
模塊中的,所以要讓修改abs
變量的指向在其它模塊也生效,要用import builtins; builtins.abs = 10
。
3. 傳入函數(shù)
既然變量可以指向函數(shù),函數(shù)的參數(shù)能接收變量,那么一個函數(shù)就可以接收另一個函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)。舉一個例子:
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs) # 11
4. map() 和 reduce()
map()
函數(shù)接收兩個參數(shù),一個是函數(shù),一個是Iterable
,map
將傳入的函數(shù)依次作用到序列的每個元素,并把結(jié)果作為新的Iterator
返回。
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r)) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
由于結(jié)果r
是一個Iterator
,Iterator
是惰性序列,因此通過list()
函數(shù)讓它把整個序列都計算出來并返回一個list
。
reduce
把一個函數(shù)作用在一個序列[x1, x2, x3, ...]
上,這個函數(shù)必須接收兩個參數(shù),reduce
把結(jié)果繼續(xù)和序列的下一個元素做累積計算,其效果就是
reduce(f, [x1, x2, x3, x4]) = f(f(f((x1, x2), x3), x4)
>>> from functools import reduce
>>> reduce(lambda x,y: x+y, [1, 2, 3, 4,5])
15
5. filter()
Python
內(nèi)建的filter()
函數(shù)用于過濾序列。
和map()
類似,filter()
也接收一個函數(shù)和一個序列。和map()
不同的是,filter()
把傳入的函數(shù)依次作用于每個元素,然后根據(jù)返回值是True
還是False
決定保留還是丟棄該元素。
把一個序列中的空字符串刪掉,可以這么寫:
def not_empty(s):
return s and s.strip()
new_list = list(filter(not_empty, ['A', ' ', 'B', None, 'C', '']))
print(new_list) # ['A', 'B', 'C']
可見用filter()
這個高階函數(shù),關(guān)鍵在于正確實現(xiàn)一個“篩選”函數(shù)。
注意: 到filter()
函數(shù)返回的是一個Iterator
,也就是一個惰性序列,所以要強迫filter()
完成計算結(jié)果,需要用list()
函數(shù)獲得所有結(jié)果并返回list
。
6. sorted()
Python
內(nèi)置的sorted()
函數(shù)也是一個高階函數(shù),它還可以接收一個key
函數(shù)來實現(xiàn)自定義的排序,例如按絕對值大小排序:
>>> soretd([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
>>> soretd([36, 5, -12, 9, -21], key=abs)
>[5, 9, -12, -21, 36]
當我們在傳入函數(shù)時,有些時候,不需要顯式地定義函數(shù),直接傳入匿名函數(shù)更方便。
關(guān)鍵字lambda
表示匿名函數(shù),冒號前面表示函數(shù)參數(shù),參數(shù)可以有多個,使用逗號","分隔。匿名函數(shù)有個限制,就是只能有一個表達式,不用寫return
,返回值就是該表達式的結(jié)果且只有一個,也不能出現(xiàn)其他非表達式語句(如for
或while
)。
lambda
只是一個表達式,函數(shù)體比def
簡單很多。lambda
的主體是一個表達式,而不是一個代碼塊。僅僅能在lambda
表達式中封裝有限的邏輯進去。lambda
函數(shù)擁有自己的命名空間,且不能訪問自己參數(shù)列表之外或全局命名空間里的參數(shù)。lambda
函數(shù)看起來只能寫一行,卻不等同于C
或C++
的內(nèi)聯(lián)函數(shù),后者的目的是調(diào)用小函數(shù)時不占用棧內(nèi)存從而增加運行效率。用匿名函數(shù)有個好處,因為函數(shù)沒有名字,不必擔(dān)心函數(shù)名沖突。此外,匿名函數(shù)也是一個函數(shù)對象,也可以把匿名函數(shù)賦值給一個變量,再利用變量來調(diào)用該函數(shù):
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同樣,也可以把匿名函數(shù)作為返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
了解更多,可以閱讀返回函數(shù)與閉包、裝飾器和偏函數(shù)