第5章 函數(shù)和函數(shù)式編程
5.1 引言
函數(shù)是組織好的穷缤,可重復(fù)使用的,用來實現(xiàn)單一拾枣,或相關(guān)聯(lián)功能的代碼段。函數(shù)能提高應(yīng)用的模塊性盒让,和代碼的重復(fù)利用率梅肤。在Python中有很多內(nèi)建函數(shù),比如print()邑茄。當(dāng)然隨著學(xué)習(xí)的深入姨蝴,你也可以學(xué)會創(chuàng)建對自己有用的函數(shù)(用戶自定義函數(shù))。簡單的理解下函數(shù)的概念肺缕,就是用戶編寫了一些語句左医,為了方便使用這些語句,把這些語句組合在一起同木,給它起一個名字浮梢。使用的時候只要調(diào)用這個名字,就可以實現(xiàn)語句組的功能了彤路。
在沒用過函數(shù)之前秕硝,我們要計算一個數(shù)的冪時會用到**,方法是這樣的:
>>>2**3
8 #此處為[**python 函數(shù)返回值**](http://www.iplaypy.com/jinjie/return.html "python 返回值")
現(xiàn)在知道了函數(shù)洲尊,就可以用內(nèi)建函數(shù)pow來計算乘方了:
>>>pow(2,3)
8
5.2 調(diào)用函數(shù)
python系統(tǒng)中自帶的一些函數(shù)就叫做內(nèi)建函數(shù)远豺,比如:dir()奈偏、type()等等,不需要我們自己編寫憋飞。還有一種是第三方函數(shù)霎苗,就是其它程序員編好的一些函數(shù),共享給大家使用榛做。這兩種函數(shù)都是拿來就可以直接使用的唁盏。最后就是我們自己編些的方便自己工作學(xué)習(xí)用的函數(shù),就叫做自定義函數(shù)了检眯。
Python內(nèi)置了很多有用的函數(shù)厘擂,我們可以直接調(diào)用。要調(diào)用一個函數(shù)锰瘸,需要知道函數(shù)的名稱和參數(shù)刽严,比如求絕對值的函數(shù)abs
,只有一個參數(shù)避凝∥杼眩可以直接從Python的官方網(wǎng)站查看文檔:
http://docs.python.org/3/library/functions.html#abs
也可以在交互式命令行通過help(abs)
查看abs
函數(shù)的幫助信息。
調(diào)用abs
函數(shù):
>>> abs(100)
100
>>> abs(-10)
10
比如max函數(shù)可以接收任意多個參數(shù)管削,并返回最大的那個:
>>> max(1, 2)
2
>>> max(-2, 0, 1, 4)
4
同時在調(diào)用函數(shù)的時候倒脓,需要注意傳入的參數(shù)數(shù)量是否符合要求。例如當(dāng)調(diào)用abs函數(shù)時含思,若傳入的參數(shù)數(shù)量不對崎弃,會報出TypeError的錯誤,并且Python會明確地告訴你:abs()有且僅有1個參數(shù)含潘,但給出了兩個:
>>> abs(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)
如果傳入的參數(shù)數(shù)量是對的饲做,但參數(shù)類型不能被函數(shù)所接受,也會報TypeError的錯誤遏弱,并且給出錯誤信息:str是錯誤的參數(shù)類型:
>>> abs('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
數(shù)據(jù)類型轉(zhuǎn)換
Python內(nèi)置的常用函數(shù)還包括數(shù)據(jù)類型轉(zhuǎn)換函數(shù)盆均,比如int()函數(shù)可以把其他數(shù)據(jù)類型轉(zhuǎn)換為整數(shù):
>>> int('12')
12
>>> int(12.3)
12
>>> float('12.3')
12.3
>>> str(1.23)
'1.23'
>>> str(10)
'10'
>>> bool(1)
True
>>> bool('')
False
也可以把函數(shù)名賦給一個變量,相當(dāng)于給這個函數(shù)起了一個“別名”:
>>> a = abs # 變量a指向abs函數(shù)
>>> a(-1) # 通過a調(diào)用abs函數(shù)
1
定義一個函數(shù):給了函數(shù)一個名稱漱逸,指定了函數(shù)里包含的參數(shù)缀踪,和代碼塊結(jié)構(gòu)。
這個函數(shù)的基本結(jié)構(gòu)完成以后虹脯,用戶可以通過另一個函數(shù)調(diào)用執(zhí)行,也可以直接從 Python 命令提示符執(zhí)行奏候。
如下實例調(diào)用了 printme() 函數(shù):
#!/usr/bin/python3
# 定義函數(shù)
def printme( str ):
"打印傳入的字符串"
print (str);
return;
# 調(diào)用函數(shù)
printme("調(diào)用用戶自定義函數(shù)!");
printme("再次調(diào)用同一函數(shù)");
調(diào)用用戶自定義函數(shù)!
再次調(diào)用同一函數(shù)
小結(jié)
調(diào)用Python的函數(shù)循集,需要根據(jù)函數(shù)定義,傳入正確的參數(shù)蔗草。
如果函數(shù)調(diào)用出錯咒彤,一定要注意看錯誤信息疆柔!
5.3 定義函數(shù)
在Python中,定義一個具有特定功能的函數(shù)需要符合一定規(guī)則:
- 函數(shù)代碼塊以 def 關(guān)鍵詞開頭镶柱,后接函數(shù)標(biāo)識符名稱和圓括號 ()旷档。
- 任何傳入?yún)?shù)和自變量必須放在圓括號中間,圓括號之間可以用于定義參數(shù)歇拆。
- 函數(shù)的第一行語句可以選擇性地使用文檔字符串—用于存放函數(shù)說明鞋屈。
函數(shù)內(nèi)容以冒號起始,并且縮進故觅。 - return [表達式] 結(jié)束函數(shù)厂庇,選擇性地返回一個值給調(diào)用方。不帶表達式的return相當(dāng)于返回 None输吏。
一般格式如下:
def 函數(shù)名(參數(shù)列表):
函數(shù)體
默認情況下权旷,參數(shù)值和參數(shù)名稱是按函數(shù)聲明中定義的的順序匹配起來的。
我們以自定義一個輸出"Hello World贯溅!"的函數(shù)為例:
>>> def hello() :
print("Hello World!")
>>> hello()
Hello World!
在更復(fù)雜的應(yīng)用中拄氯,函數(shù)中帶上參數(shù)變量:
#!/usr/bin/python3
# 計算面積函數(shù)
def area(width, height):
return width * height
def print_welcome(name):
print("Welcome", name)
print_welcome("Runoob")
w = 4
h = 5
print("width =", w, " height =", h, " area =", area(w, h))
Welcome Runoob
width = 4 height = 5 area = 20
注意:函數(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)
如果用戶已經(jīng)把hello()
的函數(shù)定義保存為test.py
文件了,那么腌乡,可以在該文件的當(dāng)前目錄下啟動Python解釋器盟劫,用from test import hello
來導(dǎo)入hello()
函數(shù),注意test
是文件名(不含.py
擴展名):
>>> from test import hello
>>> hello()
Hello World!
import
的用法在后續(xù)“模塊”一節(jié)中會詳細介紹与纽。
空函數(shù)
如果想定義一個什么事也不做的空函數(shù)侣签,可以用pass語句:
def nop():
pass
pass可以用來作為占位符,比如現(xiàn)在還沒想好怎么寫函數(shù)的代碼急迂,就可以先放一個pass影所,讓代碼能運行起來。
參數(shù)傳遞
在 python 中僚碎,類型屬于對象猴娩,變量是沒有類型的:
a=[1,2,3]
a="Runoob"
以上代碼中,[1,2,3] 是 List 類型,"Runoob" 是 String 類型卷中,而變量 a 是沒有類型矛双,她僅僅是一個對象的引用(一個指針),可以是指向 List 類型對象蟆豫,也可以是指向 String 類型對象议忽。
可更改(mutable)與不可更改(immutable)對象
在 python 中,strings, tuples, 和 numbers 是不可更改的對象十减,而 list,dict 等則是可以修改的對象栈幸。
- 不可變類型:變量賦值 a=5 后再賦值 a=10,這里實際是新生成一個 int 值對象 10嫉称,再讓 a 指向它侦镇,而 5 被丟棄,不是改變a的值织阅,相當(dāng)于新生成了a壳繁。
- 可變類型:變量賦值 la=[1,2,3,4] 后再賦值 la[2]=5 則是將 list la 的第三個元素值更改,本身la沒有動荔棉,只是其內(nèi)部的一部分值被修改了闹炉。
python 函數(shù)的參數(shù)傳遞:
- 不可變類型:類似 c++ 的值傳遞,如 整數(shù)润樱、字符串渣触、元組。如fun(a)壹若,傳遞的只是a的值嗅钻,沒有影響a對象本身。比如在 fun(a)內(nèi)部修改 a 的值店展,只是修改另一個復(fù)制的對象养篓,不會影響 a 本身。
- 可變類型:類似 c++ 的引用傳遞赂蕴,如 列表柳弄,字典。如 fun(la)概说,則是將 la 真正的傳過去碧注,修改后fun外部的la也會受影響。
python 中一切都是對象糖赔,嚴格意義我們不能說值傳遞還是引用傳遞萍丐,我們應(yīng)該說傳不可變對象和傳可變對象。
python 傳不可變對象實例
#!/usr/bin/python3
def ChangeInt( a ):
a = 10
b = 2
ChangeInt(b)
print( b ) # 結(jié)果是 2
實例中有 int 對象 2放典,指向它的變量是 b碉纺,在傳遞給 ChangeInt 函數(shù)時船万,按傳值的方式復(fù)制了變量 b,a 和 b 都指向了同一個 Int 對象骨田,在 a=10 時,則新生成一個 int 值對象 10声怔,并讓 a 指向它态贤。
傳可變對象實例
可變對象在函數(shù)里修改了參數(shù),那么在調(diào)用這個函數(shù)的函數(shù)里醋火,原始的參數(shù)也被改變了悠汽。例如:
#!/usr/bin/python3
# 可寫函數(shù)說明
def changeme( mylist ):
"修改傳入的列表"
mylist.append([1,2,3,4]);
print ("函數(shù)內(nèi)取值: ", mylist)
return
# 調(diào)用changeme函數(shù)
mylist = [10,20,30];
changeme( mylist );
print ("函數(shù)外取值: ", mylist)
傳入函數(shù)的和在末尾添加新內(nèi)容的對象用的是同一個引用。故輸出結(jié)果如下:
函數(shù)內(nèi)取值: [10, 20, 30, [1, 2, 3, 4]]
函數(shù)外取值: [10, 20, 30, [1, 2, 3, 4]]
小結(jié)
定義函數(shù)時芥驳,需要確定函數(shù)名和參數(shù)個數(shù)柿冲;
如果有必要,可以先對參數(shù)的數(shù)據(jù)類型做檢查兆旬;
函數(shù)體內(nèi)部可以用return隨時返回函數(shù)結(jié)果假抄;
函數(shù)執(zhí)行完畢也沒有return語句時,自動return None丽猬。
5.4 函數(shù)的參數(shù)
在定義函數(shù)時宿饱,我們把參數(shù)的名字和位置確定后,函數(shù)的接口定義就完成了脚祟。對于函數(shù)的調(diào)用者來說谬以,只需知道如何傳遞正確的參數(shù),以及函數(shù)的返回值即可由桌,函數(shù)內(nèi)部被封裝起來为黎,調(diào)用者無需了解。
Python的函數(shù)定義非常簡單行您,但靈活度卻非常大铭乾。除了正常定義的必選參數(shù)外,還可以使用默認參數(shù)邑雅、可變參數(shù)和關(guān)鍵字參數(shù)片橡,使得函數(shù)定義出來的接口,不但能處理復(fù)雜的參數(shù)淮野,還可以簡化調(diào)用者的代碼捧书。
鑒于函數(shù)定義中可能包含多個形參,因此函數(shù)調(diào)用中也可能包含多個實參骤星。想函數(shù)傳遞實參的方式很多经瓷,可使用位置實參,和要求實參的順序相同洞难;也可使用關(guān)鍵字實參舆吮,其中每個實參都由變量名和值組成,等等。以下是調(diào)用函數(shù)時可使用的正式參數(shù)類型:
位置參數(shù)
5.4.1位置實參
你調(diào)用函數(shù)時色冀,Python必須將函數(shù)調(diào)用中的每個實參都關(guān)聯(lián)到函數(shù)定義的一個形參潭袱。為此,最簡單的關(guān)聯(lián)方式是基于實參的順序锋恬,這種關(guān)聯(lián)方式被稱為位置實參屯换,可以多次調(diào)用。
為明白其中的工作原理与学,來看一個顯示學(xué)生信息的函數(shù)彤悔。這個函數(shù)指出一名學(xué)生的名字以及年齡,如下所示:
? def describe_student(person_name, student_age):
"""顯示學(xué)生的信息"""
print("\nMy name is " + person_name + ".")
print(person_name + " is " + student_age+ " years old.")
? describe_student('Jack', '18')
這個函數(shù)的定義表明索守,它需要一名學(xué)生的姓名和一個年齡數(shù)字(見?)晕窑。調(diào)用describe_student() 時,需要按順序提供一名學(xué)生的姓名和一個年齡數(shù)字卵佛。例如杨赤,在前面的函數(shù)調(diào)用中,實參'Jack' 存儲在形參person_name中级遭,而實參'18' 存儲在形參student_age 中(見?)望拖。在函數(shù)體內(nèi),使用了這兩個形參來顯示學(xué)生的信息挫鸽。輸出描述了一名18歲的學(xué)生Jack:
My name is Jack.
Jack is 18 years old.
- 調(diào)用函數(shù)多次
用戶可以根據(jù)需要調(diào)用函數(shù)任意次说敏。要再描述一名學(xué)生,只需再次調(diào)用describe_student() 即可:
def describe_student(person_name, student_age):
"""顯示學(xué)生的信息"""
print("\nMy name is " + person_name + ".")
print(person_name + " is " + student_age+ " years old.")
describe_student('Jack', '18')
describe_student('Bob', '17')
第二次調(diào)用describe_student() 函數(shù)時丢郊,我們向它傳遞了實參'Bob' 和'17' 盔沫。與第一次調(diào)用時一樣,Python將實參'Bob' 關(guān)聯(lián)到形參person_name枫匾,并將實參'17' 關(guān)聯(lián)到形參student_age 架诞。與前面一樣,這個函數(shù)完成其任務(wù)干茉,但打印的是一名17歲的學(xué)生Bob的信息谴忧。至此,我們描述了一名18歲的學(xué)生Jack和17歲的學(xué)生Bob.
My name is Jack.
Jack is 18 years old.
My name is Bob.
Jack is 17 years old.
調(diào)用函數(shù)多次是一種效率極高的工作方式角虫。我們只需在函數(shù)中編寫描述學(xué)生的代碼一次沾谓,然后每當(dāng)需要描述新學(xué)生時,都可調(diào)用這個函數(shù)戳鹅,并向它提供新學(xué)生的信息均驶。即便描述全校的學(xué)生,用戶依然只需使用一行調(diào)用函數(shù)的代碼枫虏,就可描述一名新學(xué)生妇穴。
在函數(shù)中爬虱,可根據(jù)需要使用任意數(shù)量的位置實參,Python將按順序?qū)⒑瘮?shù)調(diào)用中的實參關(guān)聯(lián)到函數(shù)定義中相應(yīng)的形參腾它。
- 位置實參的順序很重要
使用位置實參來調(diào)用函數(shù)時跑筝,如果實參的順序不正確,結(jié)果可能出乎意料:
def describe_student(person_name, student_age):
"""顯示學(xué)生的信息"""
print("\nMy name is " + person_name + ".")
print(person_name + " is " + student_age+ " years old.")
describe_student('18', 'Jack')
在這個函數(shù)調(diào)用中携狭,我們先指定名字继蜡,再指定學(xué)生年齡。由于實參'18' 在前逛腿,這個值將存儲到形參person_name中;同理仅颇,'Jack' 將存儲到形參student_age中单默。結(jié)果是我們得到了一名年齡為Jack的18:
My name is 18.
Jack is Jack years old.
如果結(jié)果像上面一樣荒謬,請確認函數(shù)調(diào)用中實參的順序與函數(shù)定義中形參的順序一致忘瓦。
5.4.2 默認參數(shù)
編寫函數(shù)時搁廓,可給每個形參指定默認值 。在調(diào)用函數(shù)中給形參提供了實參時耕皮,Python將使用指定的實參值境蜕;否則,將使用形參的默認值凌停。因此粱年,給形參指定默認值后,可在函數(shù)調(diào)用中省略相應(yīng)的實參罚拟。使用默認值可簡化函數(shù)調(diào)用台诗,還可清楚地指出函數(shù)的典型用法。
例如describe_student函數(shù)定義沒有問題赐俗,但若如下調(diào)用會出現(xiàn)錯誤:
>>> describe_student('Jack')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: describe_student() missing 1 required positional argument: 'student_age'
Python的錯誤信息很明確:調(diào)用函數(shù)describe_student()缺少了一個位置參數(shù)student_age拉队。這個時候,默認參數(shù)就排上用場了阻逮。若大部分學(xué)生的年齡為18歲粱快,我們可以把第二個參數(shù)student_age的默認值設(shè)定為18:
def describe_student(person_name, student_age=18):
"""顯示學(xué)生的信息"""
print("\nMy name is " + person_name + ".")
print(person_name + " is " + student_age+ " years old.")
這樣,當(dāng)我們調(diào)用describe_student(Jack)時叔扼,相當(dāng)于調(diào)用describe_student(Jack,18):
>>> describe_student('Jack')
My name is Jack.
Jack is 18 years old.
>>> describe_student('Jack','18')
My name is Jack.
Jack is 18 years old.
而對于student > 18的其他情況事哭,就必須明確地傳入student_age,比如describe_student(Herbie,19)币励。
從上面的例子可以看出慷蠕,默認參數(shù)可以簡化函數(shù)的調(diào)用。
注意:
設(shè)置默認參數(shù)時食呻,必選參數(shù)在前流炕,默認參數(shù)在后澎现,否則Python的解釋器會報錯(思考一下為什么默認參數(shù)不能放在必選參數(shù)前面)
當(dāng)函數(shù)有多個參數(shù)時,把變化大的參數(shù)放前面每辟,變化小的參數(shù)放后面剑辫。變化小的參數(shù)就可以作為默認參數(shù)。
使用默認參數(shù)有什么好處渠欺?最大的好處是能降低調(diào)用函數(shù)的難度妹蔽。
舉個例子,我們編寫一個學(xué)生注冊的函數(shù)挠将,需要傳入name和gender兩個參數(shù):
def enroll(name, gender):
"""注冊學(xué)生的信息"""
print("name: ",name)
print("gender: ",gender)
這樣胳岂,調(diào)用enroll()函數(shù)只需要傳入兩個參數(shù):
>>> enroll('Jack', 'F')
name: Jack
gender: F
如果要繼續(xù)傳入年齡、城市等信息怎么辦舔稀?這樣會使得調(diào)用函數(shù)的復(fù)雜度大大增加乳丰。
我們可以把年齡和城市設(shè)為默認參數(shù):
def enroll(name, gender,age=18, city='Beijing'):
print('name: ', name)
print('gender: ', gender)
print('age: ', age)
print('city:', city)
這樣,大多數(shù)學(xué)生注冊時不需要填寫年齡和城市内贮,只提供必須的兩個參數(shù):
>>> enroll('Sarah', 'F')
name: Sarah
gender: F
age: 18
city: Beijing
只有與默認參數(shù)不符的學(xué)生才需要提供額外的信息:
enroll('Bob', 'M', 17)
enroll('Adam', 'M', city='Tianjin')
可見产园,默認參數(shù)降低了函數(shù)調(diào)用的難度,而一旦需要更復(fù)雜的調(diào)用時夜郁,又可以傳遞更多的參數(shù)來實現(xiàn)什燕。無論是簡單調(diào)用還是復(fù)雜調(diào)用,函數(shù)只需要定義一個竞端。
有多個默認參數(shù)時屎即,調(diào)用的時候,既可以按順序提供默認參數(shù)婶熬,比如調(diào)用enroll('Bob', 'M', 17)剑勾,意思是,除了name赵颅,gender這兩個參數(shù)外奠涌,最后一個參數(shù)應(yīng)用在參數(shù)age上船老,city參數(shù)由于沒有提供,仍然使用默認值。也可以不按順序提供部分默認參數(shù)灭美。當(dāng)不按順序提供部分默認參數(shù)時杠袱,需要把參數(shù)名寫上赡茸。比如調(diào)用enroll('Adam', 'M', city='Tianjin')审葬,意思是,city參數(shù)用傳進去的值拔鹰,其他默認參數(shù)繼續(xù)使用默認值仪缸。
默認參數(shù)很有用,但定義默認參數(shù)時要牢記一點:默認參數(shù)必須指向不變對象列肢!否則會出現(xiàn)重大錯誤恰画,例如:
先定義一個函數(shù)宾茂,傳入一個list,添加一個END再返回:
def test_add(H=[]):
H.append('END')
return H
當(dāng)正常調(diào)用時拴还,結(jié)果似乎不錯:
>>> test_add([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['a', 'b', 'c'])
['a', 'b', 'c', 'END']
當(dāng)初次使用默認參數(shù)調(diào)用時跨晴,結(jié)果也是對的:
>>> test_add()
['END']
但是當(dāng)再次調(diào)用test_add()時,結(jié)果就出現(xiàn)錯誤了:
>>> test_add()
['END', 'END']
>>> test_add()
['END', 'END', 'END']
我們會發(fā)現(xiàn)片林,默認參數(shù)是[]端盆,但是函數(shù)test_add()似乎每次都“記住了”上次添加了'END'后的list。這是為什么呢费封?原因如下:
Python函數(shù)在定義的時候焕妙,默認參數(shù)H的值就被計算出來了,即[]弓摘,因為默認參數(shù)H也是一個變量访敌,它指向?qū)ο骩],每次調(diào)用該函數(shù)衣盾,如果改變了H的內(nèi)容,則下次調(diào)用時爷抓,默認參數(shù)的內(nèi)容就變了势决,不再是函數(shù)定義時的[]了。
注意:定義默認參數(shù)時蓝撇,默認參數(shù)必須指向不變對象果复!
我們可以用None這個不變對象來解決此問題:
def test_add(H=None):
if H is None:
H = []
H.append('END')
return H
現(xiàn)在,無論調(diào)用多少次渤昌,都不會有問題:
>>> test_add()
['END']
>>> test_add()
['END']
為什么要設(shè)計str虽抄、None這樣的不變對象呢?因為不變對象一旦創(chuàng)建独柑,對象內(nèi)部的數(shù)據(jù)就不能修改迈窟,這樣就減少了由于修改數(shù)據(jù)導(dǎo)致的錯誤。此外忌栅,由于對象不變车酣,多任務(wù)環(huán)境下同時讀取對象不需要加鎖,同時讀一點問題都沒有索绪。我們在編寫程序時湖员,如果可以設(shè)計一個不變對象,那就盡量設(shè)計成不變對象瑞驱。
5.4.3 不定長參數(shù)
在Python函數(shù)中娘摔,還可以定義不定長參數(shù),也叫可變參數(shù)唤反。顧名思義凳寺,不定長參數(shù)就是傳入的參數(shù)個數(shù)是可變的鸭津。
我們以數(shù)學(xué)題為例子,給定一組數(shù)字a读第,b曙博,c……,請計算a+b+c+ ……要定義出這個函數(shù)怜瞒,我們必須確定輸入的參數(shù)父泳。由于參數(shù)個數(shù)不確定,我們首先想到可以把a吴汪,b惠窄,c……作為一個list或tuple傳進來,這樣漾橙,函數(shù)可以定義如下:
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
但是調(diào)用的時候杆融,需要先組裝出一個list或tuple:
>>> calc([1, 2, 3])
6
>>> calc((1, 2, 3, 4))
10
但若把函數(shù)的參數(shù)改為不定長參數(shù):
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
則調(diào)用函數(shù)的方式可以簡化成這樣:
>>> calc(1, 2, 3)
6
>>> calc(1, 2, 3, 4)
10
定義不定長參數(shù)和定義一個list或tuple參數(shù)相比,僅僅在參數(shù)前面加了一個*號霜运。在函數(shù)內(nèi)部脾歇,參數(shù)numbers接收到的是一個tuple,因此淘捡,函數(shù)代碼完全不變藕各。但是,調(diào)用該函數(shù)時焦除,可以傳入任意個參數(shù)激况,包括0個參數(shù):
>>> calc()
0
如果已經(jīng)有一個list或者tuple,要調(diào)用一個不定長參數(shù)可如下:
>>> nums = [1, 2, 3]
>>> calc(*nums)
6
*nums表示把nums這個list的所有元素作為可變參數(shù)傳進去膘魄。這種寫法相當(dāng)有用乌逐,而且很常見。
5.4.4 關(guān)鍵字參數(shù)
關(guān)鍵字實參是傳遞給函數(shù)的名稱—值對创葡。你直接在實參中將名稱和值關(guān)聯(lián)起來了浙踢,因此向函數(shù)傳遞實參時不會混淆(不會得到名為18的Jack這樣的結(jié)果)。關(guān)鍵字實參讓用戶無需考慮函數(shù)調(diào)用中的實參順序蹈丸,還清楚地指出了函數(shù)調(diào)用中各個值的用途成黄。
下面重新編寫describe_student()函數(shù),在其中使用關(guān)鍵字實參來調(diào)用describe_student():
def describe_student(person_name, student_age):
"""顯示學(xué)生的信息"""
print("\nMy name is " + person_name + ".")
print(person_name + " is " + student_age+ " years old.")
describe_student(person_name='Jack', student_age='18')
函數(shù)describe_pet() 還是原來那樣逻杖,但調(diào)用這個函數(shù)時奋岁,我們向Python明確地指出了各個實參對應(yīng)的形參≥┌伲看到這個函數(shù)調(diào)用時闻伶,Python知道應(yīng)該將實參'Jack' 和'18' 分別存儲在形參person_name和 student_age中。輸出正確無誤够话,它指出我們有一名名叫Jack蓝翰,年齡為18歲的學(xué)生光绕。關(guān)鍵字實參的順序無關(guān)緊要,因為Python知道各個值該存儲到哪個形參中畜份。下面兩個函數(shù)調(diào)用是等效的:
describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster')
注意:使用關(guān)鍵字實參時诞帐,務(wù)必準確地指定函數(shù)定義中的形參名。
不定長參數(shù)允許傳入0個或任意個參數(shù)爆雹,這些不定長參數(shù)在函數(shù)調(diào)用時自動組裝為一個元組(tuple)停蕉。而關(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ù):
def enroll(name, age,**kw)):
print('name: ', name, 'age: ', age, 'other: ', kw)
>>> enroll('Michael', 18)
name: Michael age: 18 other: {}
也可以傳入任意個數(shù)的關(guān)鍵字參數(shù):
>>> enroll('Bob', 17, city='Beijing')
name: Bob age: 17 other: {'city': 'Beijing'}
>>> enroll('Adam', 19, gender='M', job='Engineer')
name: Adam age: 19 other: {'gender': 'M', 'job': 'Engineer'}
關(guān)鍵字參數(shù)有擴展函數(shù)的功能。比如驻子,在enroll函數(shù)里灿意,我們保證能接收到name和age這兩個參數(shù),但是崇呵,如果調(diào)用者愿意提供更多的參數(shù)脾歧,我們也能收到。試想你正在做一個用戶注冊的功能演熟,除了用戶名和年齡是必填項外,其他都是可選項司顿,利用關(guān)鍵字參數(shù)來定義這個函數(shù)就能滿足注冊的需求芒粹。
和不定長參數(shù)類似,也可以先組裝出一個dict大溜,然后化漆,把該dict轉(zhuǎn)換為關(guān)鍵字參數(shù)傳進去:
>>> extra = {'gender':'M','city': 'Beijing'}
>>> enroll('Jack', 18, gender=extra['M'], city=extra['city'])
name: Jack age: 18 other: {'gender':'M','city': 'Beijing'}
當(dāng)然,上面復(fù)雜的調(diào)用可以用簡化的寫法:
>>> extra = {'gender':'M','city': 'Beijing'}
>>> enroll('Jack', 18, **extra)
name: Jack age: 18 other: {'gender':'M','city': 'Beijing'}
注意:**extra
表示把extra這個dict的所有key-value用關(guān)鍵字參數(shù)傳入到函數(shù)的**kw參數(shù)钦奋,kw將獲得一個dict座云,注意kw獲得的dict是extra的一份拷貝,對kw的改動不會影響到函數(shù)外的extra付材。
5.4.5 命名關(guān)鍵字參數(shù)
如果要限制關(guān)鍵字參數(shù)的名字朦拖,就可以用命名關(guān)鍵字參數(shù)。
和關(guān)鍵字參數(shù)**kw
不同厌衔,如果沒有可變參數(shù)璧帝,命名關(guān)鍵字參數(shù)就必須加一個*
作為特殊分隔符。如果缺少*
富寿,Python解釋器將無法識別位置參數(shù)和命名關(guān)鍵字參數(shù)睬隶。例如锣夹,若只接收age和city作為關(guān)鍵字參數(shù)。這種方式定義的函數(shù)如下:
def enroll(name, gender, *, age, city):
print(name, gender, age, city)
>>> enroll('Jack', ' M', age='18', city='Beijing')
Jack M 18 Beijing
如果函數(shù)定義中已經(jīng)有了一個可變參數(shù)苏潜,后面跟著的命名關(guān)鍵字參數(shù)就不再需要一個特殊分隔符*了:
def enroll(name, gender, *grade, age, city):
print(name, gender, grader, age, city)
注意:和位置參數(shù)不同银萍,命名關(guān)鍵字參數(shù)必須傳入?yún)?shù)名。
如果沒有傳入?yún)?shù)名恤左,調(diào)用將報錯:
>>> enroll(' Jack',' M',' 18',' Beijing')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: enroll() takes 2 positional arguments but 4 were given
由報錯信息可知贴唇,由于調(diào)用時缺少參數(shù)名age和city,Python解釋器把這4個參數(shù)均視為位置參數(shù)赃梧,但enrroll()函數(shù)僅接受2個位置參數(shù)滤蝠。
注意:命名關(guān)鍵字參數(shù)可以有缺省值。
由于命名關(guān)鍵字參數(shù)city具有默認值授嘀,調(diào)用時可不傳入city參數(shù):
def enroll(name, gender, *, age='18', city):
print(name, gender, age, city)
>>> enroll('Jack','M',city='Beijing')
Jack M 18 Beijing
5.4.6 參數(shù)組合
現(xiàn)在我們知道python定義函數(shù)的參數(shù)類型有:
必選參數(shù)物咳、默認參數(shù)、可變參數(shù)蹄皱、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù)
在Python中定義函數(shù)览闰,我們是可以組合使用這些參數(shù)的。但要注意的是巷折,參數(shù)定義是有順序的压鉴,定義的順序必須是:必選參數(shù)、默認參數(shù)锻拘、可變參數(shù)油吭、命名關(guān)鍵字參數(shù)和關(guān)鍵字參數(shù)。
比如定義一個函數(shù)署拟,包含上述若干種參數(shù):
def func(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
在函數(shù)調(diào)用的時候婉宰,Python解釋器自動按照參數(shù)位置和參數(shù)名把對應(yīng)的參數(shù)傳進去。
>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> func(1, 2, 3, 'a', 'b', x=4)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 4}
另外對于任意函數(shù)推穷,都可以通過類似 func(*args, **kw) 的形式調(diào)用它心包,無論它的參數(shù)是如何定義的。以一個元組(tuple)和字典(dict)為例:
>>> args = (1, 2, 3, 4)
>>> kw = {'x': 5}
>>> func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 5}
小結(jié)
Python的函數(shù)具有非常靈活的參數(shù)形態(tài)馒铃,既可以實現(xiàn)簡單的調(diào)用蟹腾,又可以傳入非常復(fù)雜的參數(shù)。
默認參數(shù)一定要用不可變對象区宇,如果是可變對象娃殖,運行會有邏輯錯誤!
要注意定義可變參數(shù)和關(guān)鍵字參數(shù)的語法:
args 是可變參數(shù)议谷,args接收的是一個tuple珊随;
kw 是關(guān)鍵字參數(shù),kw接收的是一個dict。
以及調(diào)用函數(shù)時如何傳入可變參數(shù)和關(guān)鍵字參數(shù)的語法:
可變參數(shù)既可以直接傳入: func(1, 2, 3) 叶洞,又可以先組裝list或tuple鲫凶,再通過*args
傳入: func((1, 2, 3)) ;
關(guān)鍵字參數(shù)既可以直接傳入: func(a=1, b=2) 衩辟,又可以先組裝dict螟炫,再通過 **kw
傳入: func({'a': 1, 'b': 2}) 。
使用 *args
和**kw
是Python的習(xí)慣寫法艺晴,當(dāng)然也可以用其他參數(shù)名昼钻,但最好使用習(xí)慣用法
5.6 返回值
函數(shù)并非總是直接顯示輸出,相反封寞,它可以處理一些數(shù)據(jù)然评,并返回一個或一組值。函數(shù)返回的值被稱為返回值 狈究。在函數(shù)中碗淌,可使用return 語句將值返回到調(diào)用函數(shù)的代碼行。返回值讓你能夠?qū)⒊绦虻拇蟛糠址敝毓ぷ饕频胶瘮?shù)中去完成抖锥,從而簡化主程序亿眠。
return [表達式] 語句用于退出函數(shù),選擇性地向調(diào)用方返回一個表達式磅废。不帶參數(shù)值的return語句返回None纳像。以下實例演示了 return 語句的用法:
#!/usr/bin/python3
def sum( arg1, arg2 ):
total = arg1 + arg2 # 返回2個參數(shù)的和
print ("函數(shù)內(nèi) : ", total)
return total;
total = sum( 10, 20 );
print ("函數(shù)外 : ", total)
函數(shù)內(nèi) : 30
函數(shù)外 : 30
5.7 函數(shù)式編程
函數(shù)式編程是一種編程范式,我們常見的編程范式有命令式編程拯勉、函數(shù)式編程竟趾、邏輯式編程,常見的面向?qū)ο缶幊桃彩且环N命令式編程宫峦。命令式編程是面向計算機硬件的抽象潭兽,在計算機的層次上,CPU執(zhí)行的是加減乘除的指令代碼斗遏,以及各種條件判斷和跳轉(zhuǎn)指令。而函數(shù)式編程是面向數(shù)學(xué)的抽象鞋邑,將計算描述為一種表達式求值诵次,越是抽象的計算,離計算機硬件越遠枚碗。
值得注意的是逾一,函數(shù)式編程中的“函數(shù)”不是指計算機中的函數(shù),而是指數(shù)學(xué)中的函數(shù)肮雨,即自變量的映射遵堵,一個函數(shù)的值僅決定于函數(shù)參數(shù)的值,不依賴其它狀態(tài)。在編程式語言中陌宿,“函數(shù)”可以在任何地方定義锡足,在函數(shù)內(nèi)或函數(shù)外,可以作為函數(shù)的參數(shù)或返回值壳坪,可以對函數(shù)進行組合舶得。純函數(shù)式編程中的變量也不是命令式編程語言中的變量,而是代數(shù)中的變量爽蝴,即一個值的名稱沐批,變量的值是不可變的蝎亚,比如命令式編程中的"x = x+1"這種依賴可變狀態(tài)的事實此時被認為為假。
例:通過別的名稱使用函數(shù)躺彬,再把函數(shù)作為參數(shù)傳遞
>>> fact = factorial
>>> fact
<function factorial at >
>>> func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 5}
函數(shù)式編程最主要的好處主要是不可變性帶來的,函數(shù)是“引用透明”的缤底,并可避免“副作用”,它不會依賴也不會改變當(dāng)前函數(shù)以外的數(shù)據(jù)江解。
高階函數(shù)
接受函數(shù)為參數(shù),或者把函數(shù)作為結(jié)果返回的函數(shù)是高階函數(shù)(higher-
order function)徙歼。map 函數(shù)就是一例犁河,此外魄梯,內(nèi)置函數(shù) sorted 也是:可選的 key 參數(shù)用于提供一個函數(shù),它會應(yīng)用到各個元素上進行排序.
例灭翔,若想根據(jù)單詞的長度排序辣苏,只需把 len 函數(shù)傳給 key 參數(shù),如下:
根據(jù)單詞長度給一個列表排序
>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=len)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']
任何單參數(shù)函數(shù)都能作為 key 參數(shù)的值煌张。例如骏融,為了創(chuàng)建押韻詞典,可以把各個單詞反過來拼寫怀泊,然后排序窃肠。注意,示例 5-4 中列表里的單詞沒有變碧囊,我們只是把反向拼寫當(dāng)作排序條件糯而,因此各種漿果(berry)都排在一起泊窘。
示例:根據(jù)反向拼寫給一個單詞列表排序
>>> def reverse(word):
... return word[::-1]
>>> reverse('testing')
'gnitset'
>>> sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
在函數(shù)式編程范式中,最為人熟知的高階函數(shù)有map瓜贾、filter携悯、reduce 和 apply憔鬼。apply 函數(shù)在 Python 2.3 中標(biāo)記為過時,在 Python 3 中移除了昌跌,因為不再需要它了照雁。如果想使用不定量的參數(shù)調(diào)用函數(shù),可以編寫 fn(*args, **keywords)饺蚊,不用再編寫 apply(fn, args, kwargs)卸勺。
5.5 匿名函數(shù)
所謂匿名烫扼,意即不再使用 def 語句這樣標(biāo)準的形式定義一個函數(shù)。python 使用 lambda 來創(chuàng)建匿名函數(shù)悟狱。
lambda 只是一個表達式挤渐,函數(shù)體比 def 簡單很多。lambda 函數(shù)的語法如下:
lambda [arg1 [,arg2,.....argn]]:expression
lambda的主體是一個表達式得问,而不是一個代碼塊宫纬,僅能在lambda表達式中封裝有限的邏輯進去膏萧。
注意:lambda 函數(shù)擁有自己的命名空間榛泛,且不能訪問自己參數(shù)列表之外或全局命名空間里的參數(shù)。
注意:雖然lambda函數(shù)看起來只能寫一行孤个,卻不等同于C或C++的內(nèi)聯(lián)函數(shù)艘希,后者的目的是調(diào)用小函數(shù)時不占用棧內(nèi)存從而增加運行效率覆享。
如下實例:
#!/usr/bin/python3
sum = lambda arg1, arg2: arg1 + arg2;
# 調(diào)用sum函數(shù)
print ("相加后的值為 : ", sum( 1, 2 ))
print ("相加后的值為 : ", sum( 2, 2 ))
相加后的值為 : 3
相加后的值為 : 4
變量作用域
Python 中撒顿,程序的變量并不是在哪個位置都可以訪問的,訪問權(quán)限決定于這個變量是在哪里賦值的吩屹。
變量的作用域決定了在哪一部分程序可以訪問哪個特定的變量名稱拧抖。Python的作用域一共有4種唧席,分別是:
L (Local) 局部作用域
E (Enclosing) 閉包函數(shù)外的函數(shù)中
G (Global) 全局作用域
B (Built-in) 內(nèi)建作用域
以 L –> E –> G –>B 的規(guī)則查找嘲驾,即:在局部找不到辽故,便會去局部外的局部找(例如閉包)腐碱,再找不到就會去全局找症见,再者去內(nèi)建中找。
x = int(2.9) # 內(nèi)建作用域
g_count = 0 # 全局作用域
def outer():
o_count = 1 # 閉包函數(shù)外的函數(shù)中
def inner():
i_count = 2 # 局部作用域
Python 中只有模塊(module)缴啡,類(class)以及函數(shù)(def业栅、lambda)才會引入新的作用域谬晕,其它的代碼塊(如 if/elif/else/攒钳、try/except、for/while等)是不會引入新的作用域的文兢,也就是說這這些語句內(nèi)定義的變量姆坚,外部也可以訪問实愚,如下代碼:
>>> if True:
... msg = 'I am from Runoob'
...
>>> msg
'I am from Runoob'
實例中 msg 變量定義在 if 語句塊中腊敲,但外部還是可以訪問的。
如果將 msg 定義在函數(shù)中懂昂,則它就是局部變量凌彬,外部不能訪問:
>>> def test():
... msg_inner = 'I am from Runoob'
...
>>> msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
從報錯的信息上看,說明了 msg_inner 未定義,無法使用原探,因為它是局部變量顽素,只有在函數(shù)內(nèi)可以使用胁出。
全局變量和局部變量
定義在函數(shù)內(nèi)部的變量擁有一個局部作用域全蝶,定義在函數(shù)外的擁有全局作用域。
局部變量只能在其被聲明的函數(shù)內(nèi)部訪問绷落,而全局變量可以在整個程序范圍內(nèi)訪問始苇。調(diào)用函數(shù)時催式,所有在函數(shù)內(nèi)聲明的變量名稱都將被加入到作用域中。如下實例:
#!/usr/bin/python3
total = 0; # 這是一個全局變量
# 可寫函數(shù)說明
def sum( arg1, arg2 ):
#返回2個參數(shù)的和."
total = arg1 + arg2; # total在這里是局部變量.
print ("函數(shù)內(nèi)是局部變量 : ", total)
return total;
#調(diào)用sum函數(shù)
sum( 10, 20 );
print ("函數(shù)外是全局變量 : ", total)
函數(shù)內(nèi)是局部變量 : 30
函數(shù)外是全局變量 : 0
global 和 nonlocal關(guān)鍵字
當(dāng)內(nèi)部作用域想修改外部作用域的變量時,就要用到global和nonlocal關(guān)鍵字了喉童。
以下實例修改全局變量 num:
#!/usr/bin/python3
num = 1
def fun1():
global num # 需要使用 global 關(guān)鍵字聲明
print(num)
num = 123
print(num)
fun1()
1
123
如果要修改嵌套作用域(enclosing 作用域堂氯,外層非全局作用域)中的變量則需要 nonlocal 關(guān)鍵字了咽白,如下實例:
#!/usr/bin/python3
def outer():
num = 10
def inner():
nonlocal num # nonlocal關(guān)鍵字聲明
num = 100
print(num)
inner()
print(num)
outer()
100
100
另外有一種特殊情況,假設(shè)下面這段代碼被運行:
#!/usr/bin/python3
a = 10
def test():
a = a + 1
print(a)
test()
以上程序執(zhí)行排抬,報錯信息如下:
Traceback (most recent call last):
File "test.py", line 7, in <module>
test()
File "test.py", line 5, in test
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment
錯誤信息為局部作用域引用錯誤蹲蒲,因為 test 函數(shù)中的 a 使用的是局部,未定義缘薛,無法修改宴胧。
全局變量和局部變量
定義在函數(shù)內(nèi)部的變量擁有一個局部作用域表锻,定義在函數(shù)外的擁有全局作用域瞬逊。
局部變量只能在其被聲明的函數(shù)內(nèi)部訪問确镊,而全局變量可以在整個程序范圍內(nèi)訪問。調(diào)用函數(shù)時敦间,所有在函數(shù)內(nèi)聲明的變量名稱都將被加入到作用域中廓块。如下實例:
#!/usr/bin/python3
total = 0 # 這是一個全局變量
# 可寫函數(shù)說明
def sum( arg1, arg2 ):
#返回2個參數(shù)的和."
total = arg1 + arg2 # total在這里是局部變量.
print ("函數(shù)內(nèi)是局部變量 : ", total)
return total
#調(diào)用sum函數(shù)
sum( 10, 20 )
print ("函數(shù)外是全局變量 : ", total)
以上實例輸出結(jié)果:
函數(shù)內(nèi)是局部變量 : 30
函數(shù)外是全局變量 : 0