Python 中的函數(shù)是由若干語句組成的語句塊、函數(shù)名稱、參數(shù)列表構(gòu)成,它是組織代碼的最小單元,可以完成一定的功能
一.函數(shù)的作用
- 結(jié)構(gòu)化編程對代碼的最基本的封裝,一般按照功能組織一段代碼
- 封裝的目的是為了復(fù)用,減少冗余代碼
- 代碼更加簡潔美觀,可讀易懂
二.函數(shù)的分類
- 內(nèi)建函數(shù) : max(),reversed()
- 庫函數(shù):math.ceil()
三.函數(shù)定義、調(diào)用
1.def關(guān)鍵字定義函數(shù)
def 函數(shù)名(形參列表):
函數(shù)體(代碼塊)
[return 返回值]
- 函數(shù)名就是標(biāo)識符,只能由英文,數(shù)字和下劃線組成,不能以數(shù)字開頭.
- python函數(shù)使用return語句返回“返回值”
- 所有函數(shù)都有返回值,如果沒有return語句,隱式調(diào)用return None
- return語句并不一定是函數(shù)的語句塊的最后一條語句
- 一個函數(shù)可以存在多個return 語句,但是只有一條可以被執(zhí)行,如果沒有return語句被執(zhí)行,使用隱式調(diào)用return None
- 如果有必要,可顯示調(diào)用return None ,可以簡寫為return
- 如果函數(shù)執(zhí)行了return 語句,函數(shù)就會返回,當(dāng)前被執(zhí)行的return語句之后其他的語句就不會被執(zhí)行
- return語句的作用:結(jié)束函數(shù)調(diào)用,返回值
- 函數(shù)不能同時返回多個值
- 函數(shù)是可調(diào)用的對象 callable()
2.函數(shù)調(diào)用
- 函數(shù)定義,只是聲明了一個函數(shù),它是不會被執(zhí)行的,需要調(diào)用
- 函數(shù)調(diào)用,需要在函數(shù)名后名加上()
- 調(diào)用函數(shù)的時候在()中寫入的參數(shù)是實際參數(shù),簡稱實參。
- 實參只有兩種傳參方式:按照形參位置順序傳參和關(guān)鍵字傳參
關(guān)鍵字傳參必須放在最后
3.函數(shù)形參
①位置參數(shù)
- def fn(x,y) 可以使用 fn(1,2) 和fn(x=1,y=2)調(diào)用
- 按照形參位置順序傳參,傳參的順序必須形參順序和個數(shù)一致。
-
關(guān)鍵字傳入實參的時候,傳參時的關(guān)鍵字必須和定義的形參名和個數(shù)一致,傳參的順序可以和形參順序不一致。
位置參數(shù).gif
② 參數(shù)默認(rèn)值(缺省值)
在定義形參的時候可以給形參賦予一個默認(rèn)值.
def fn(x=3,y=2):
print("x={},y={}".format(x,y))
- 參數(shù)的默認(rèn)值可以在未傳入足夠的實參的時候,對沒有給定的參數(shù)賦值為默認(rèn)值
- 設(shè)置參數(shù)的缺省值,可以簡化函數(shù)調(diào)用
③可變位置參數(shù)
- 在位置參數(shù)前面使用“*” 表示該形參是可變參數(shù),可以接受0到多個實參
- 收集多個實參為一個元組tuple
- 不接受關(guān)鍵字傳參
- 可變位置參數(shù)必須在位置參數(shù)后面
④keyword-only參數(shù)(python3 后加入)
- 在可變位置參數(shù)后面,出現(xiàn)的形參
- 在傳參的時候一定要使用關(guān)鍵字傳參
⑤ 可變關(guān)鍵字參數(shù)
- 在形參簽名使用“**”符號,表示可以接受0到多個關(guān)鍵字參數(shù)
- 收集的實參的關(guān)鍵字和值組成一個字典dict,該字典在函數(shù)中是可以改變的
- 如果形參中有keyword-only參數(shù),一定要在該參數(shù)后面
⑥總結(jié)
- 形參一般順序是,位置參數(shù),缺省位置參數(shù),可變位置參數(shù),keywork-only參數(shù)(可以帶缺省值),可變關(guān)鍵字參數(shù)
函數(shù)參數(shù)解構(gòu)
- 給函數(shù)提供實參的時候,可以在可迭代對象中使用* 或者**,把集合類型的解構(gòu)解開,提取出所有元素作為函數(shù)的實參
- 非字典類型使用*解構(gòu)成位置參數(shù)
- 字典類型使用 ** 解構(gòu)成關(guān)鍵字參數(shù)
- 提出出來的元素樹木要和參數(shù)的要求匹配,也要和參數(shù)的類型匹配
- 參數(shù)解構(gòu)只能用在函數(shù)中,print() 函數(shù)等

四. 函數(shù)嵌套
在一個函數(shù)中定義了另外一個函數(shù),內(nèi)部的函數(shù)不能在外部直接使用,會拋出NameError 異常。
函數(shù)有可見范圍,這就是作用域的概念

1.作用域
- 一個標(biāo)識符的可見范圍,這就是標(biāo)識符的作用域。一般常說的是變量的作用域。
作用域分為:全局作用域,和局部作用域 - 全局作用域
在整個程序運行環(huán)境中可見 - 局部作用域
在函數(shù),類等內(nèi)部可見,局部變量使用范圍不能超過其所在的局部作用域
def outer2():
o = 65
def inner():
o = 97
print("inner {}".format(o))
print("outer {}".format(o))
inner()
outer2()
#結(jié)果
#outer 65
#inner 97
通過上述嵌套結(jié)構(gòu)例子可以看出
- 外層變量作用域在內(nèi)層作用域可見
- 內(nèi)層作用域inner中,如果定義了o = 97,相當(dāng)于當(dāng)前作用域中重新定義了一個新的變量o,但是這個o并沒有 覆蓋外層作用域outer中的o。
①.全局變量globle
x = 5
def foo():
##如果不使用global,會出現(xiàn)先引用后賦值的異常報錯
global x
x += 1
print("x =",x)
foo()
print("x =",x)
###結(jié)果
#x = 6
#x = 6
- 使用global 關(guān)鍵字的變量,將函數(shù)內(nèi)的x聲明為外部的全局作用域定義的x
- 全局作用作用域中必須要有x的定義
- 使用global可以告訴內(nèi)部作用域,去全局作用域查找變量的定義,之后在函數(shù)內(nèi)對x的所有操作,都相當(dāng)于在為全局作用域的變量x操作賦值
x = 5
def foo():
global x
x += 1
print("x =",x)
x = 9
foo()
print("x =",x)
##結(jié)果
#x = 6
#x = 9
globle 使用原則
- 外部作用域變量會內(nèi)部作用域可見,但也不要在這個內(nèi)部的局部作用域中直接使用,因為函數(shù)的目的就是為了封裝,盡量與外界隔離
- 如果函數(shù)需要使用外部全局變量,請使用函數(shù)的形參傳參解決
不要用global,學(xué)習(xí)它,只是為了深入理解變量作用域?。?!
對“作用域”可以進(jìn)行如下理解:
1.在最頂層,比如shell層,有一個符號表會跟蹤記錄這一層所有的名稱定義和綁定
2.調(diào)用函數(shù)的時候,會建立一個新的符號表(常稱為棧幀)。這個表跟蹤記錄函數(shù)中所有的名稱定義(包括形參)和它們當(dāng)前的綁定。如果函數(shù)體內(nèi)又調(diào)用了一個函數(shù),就再建立一個棧幀。
3.函數(shù)結(jié)束時候,它的棧幀也隨之消息。
2.閉包
- 自由變量:未在本地作用域定義的變量。
例如:定義在內(nèi)層函數(shù)外的外層函數(shù)的作用域中的變量 - 閉包:就是一個概念,出現(xiàn)在嵌套函數(shù)中,指的是內(nèi)層函數(shù)引用到了外層函數(shù)的自由變量,這就形成了閉包.
def counter():
c = [0]
def inc():
c[0] += 1 #應(yīng)用的是自由變量正式counter的變量c
return c[0]
print(c[0])
return inc
#counter()()
foo = counter()
print(foo(),foo()) #調(diào)用的是inner()
print(foo())
這是python2 實現(xiàn)閉包的方式,python3 還可以使用nonlocal 關(guān)鍵字
①nonlocal 關(guān)鍵字
使用nonlocal 關(guān)鍵字,將變量標(biāo)記為不再本地作用域定義,而在上級的某一級局部作用域中定義,但不能是全局作用域中的定義
def counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
foo = counter()
print(foo())
print(foo())
- count 是外層函數(shù)的局部變量,被內(nèi)部函數(shù)引用
- 內(nèi)部函數(shù)使用nonlocal 關(guān)鍵字聲明count變量在上級作用域而非 本地作用域中定義
- 形成了閉包
②默認(rèn)值的作用域
def foo(xyz=[]):
xyz.append(1)
print(xyz)
foo()
foo()
###result
#[1]
#[1, 1]
- 函數(shù)也是對象,python 把函數(shù)的默認(rèn)值放在了屬性中,這個屬性就伴隨著這個函數(shù)對象的整個生命周期
- 查看foo.__defaults__屬性
([1, 1],)
def foo(xyz=[],u='abc',z=123):
xyz.append(1)
return xyz
print(foo(),id(foo))
print(foo.__defaults__)
print(foo(),id(foo))
print(foo.__defaults__)
- 函數(shù)地址并沒有變,就是說函數(shù)這個對象沒有變,調(diào)用它,它的屬性__defaults__ 中使用元組保存默認(rèn)值
- xyz 默認(rèn)值是引用類型,引用類型的元素變動,并不是元組的變化
非引用類型例子
def foo(w,u='abc',z=123):
u = 'xyz'
z = 789
print(w,u,z)
print(foo.__defaults__)
foo('wing')
print(foo.__defaults__)
屬性__defaults__中使用元組保存所有位置參數(shù)默認(rèn)值,它不會因為在函數(shù)體內(nèi)使用了它而發(fā)生改變
def foo(w,u='abc',*,z=123,zz=[456]):
u = 'xyz'
z = 789
zz.append(1)
print(w,u,z,zz)
print(foo.__defaults__)
foo('wing')
print(foo.__kwdefaults__)
- 屬性__defaults__ 中使用元組保存所有位置參數(shù)默認(rèn)值
- 屬性__kwdefaults__ 中使用字典保存所有keyword-only 參數(shù)的默認(rèn)值
- 使用可變類型作為形參默認(rèn)值的時候,就可能修改這個默認(rèn)值
1.函數(shù)體內(nèi),不改變默認(rèn)值
def foo(xyz=[],u='abc',z=123):
xyz = xyz[:] #影子拷貝
xyz.append(1)
print(xyz)
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
foo()
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
- 函數(shù)中的xyz 都是傳入?yún)?shù)或者默認(rèn)參數(shù)的副本,如果就想修改原參數(shù),不可以
2.使用不可變類型的默認(rèn)值
def foo(xyz=None,u='abc',z=123):
if xyz is None:
xyz = []
xyz.append(1)
print(xyz)
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
- 如果使用缺省值None就創(chuàng)建一個列表
- 如果傳入一個列表,就修改這個列表
3.總結(jié)
- 第一種方法
使用影子拷貝,創(chuàng)建了一個新的對象,永遠(yuǎn)不能改變傳入的參數(shù)。 - 第二種方法
通過值的判斷就可以靈活的選擇創(chuàng)建或者修改傳入的參數(shù),這種方式靈活,應(yīng)用廣泛。
很多函數(shù)的定義,都可以看到使用None這個不可變的值作為默認(rèn)參數(shù),這可以說是一種慣用法
四.變量名解析原則LEGB
- Local,本地作用域,局部作用域的local命名空間。函數(shù)調(diào)用時創(chuàng)建,調(diào)用結(jié)束消亡
- Enclosing,Python2.2時引入的嵌套函數(shù),實現(xiàn)了閉包,這個就是嵌套函數(shù)的外部函數(shù)的命名空間
- Global 全局作用域,即一個模塊的命名空間。模塊被import時創(chuàng)建,解釋器退出時消亡
- Build-in,內(nèi)置模塊的命名空間,生命周期從python解析器啟動的時創(chuàng)建到解釋器退出時消亡。例如print(open),print 和open 都是內(nèi)置的變量
- 所以一個名詞的查找順序就是LEGB
五.函數(shù)的銷毀
全局函數(shù)銷毀
- 重新定義同名函數(shù)
- del語句刪除函數(shù)對象
- 程序結(jié)束時
- 局部函數(shù)
局部函數(shù)銷毀
- 重新在上級作用域定義同名函數(shù)
- del語句刪除函數(shù)名稱,函數(shù)對象的引用計數(shù)減1
- 上級作用域銷毀時
五.文檔字符串:
""" text """
三引號之間的文本在Python中稱為文檔字符串。按照慣例,使用文檔字符串提供函數(shù)的規(guī)范??梢允褂脙?nèi)置的函數(shù)help訪問這些字符串。

六.遞歸
2.定義
** 函數(shù)直接或者間接調(diào)用自身就是遞歸**
- 遞歸需要邊界條件,遞歸前進(jìn)段,遞歸返回段
- 遞歸一定要有邊界條件
- 當(dāng)邊界條件不滿足的時候,遞歸前進(jìn)
- 當(dāng)邊界條件滿足的時候,遞歸返回
2.遞歸要求
- 遞歸一定要有退出條件,遞歸調(diào)用一定要執(zhí)行到這個退出條件。沒有退出條件的遞歸調(diào)用,就是無限調(diào)用
- 遞歸深度不宜過深
可以通過sys.getrecursionlimit()查看解釋器的深度限制
3.遞歸的性能
- 循環(huán)稍微復(fù)雜一些,但是只要不是死循環(huán),可以多次迭代直至算出結(jié)果。
- 遞歸有深度限制,如果遞歸復(fù)雜,函數(shù)反復(fù)亞棧,棧內(nèi)存很快就溢出了。
- 遞歸中可以通過形參記錄每次遞歸的計算,減少遞歸次數(shù)
4.間接遞歸:
def foo1():
foo2()
def foo2():
foo1()
foo1()
間接遞歸,是通過別的函數(shù)調(diào)用了函數(shù)自身。
但是,如果構(gòu)成了循環(huán)遞歸抵用是非常危險的,但是往往這種情況在代碼復(fù)雜的情況下,還是可能發(fā)生這種調(diào)用。要用代碼的規(guī)范來避免這種遞歸調(diào)用的發(fā)生。
5.遞歸總結(jié)
- 遞歸是一種很自然的表達(dá),符合邏輯思維
- 遞歸相對運行效率低,每一次調(diào)用函數(shù)都要開辟棧幀
- 遞歸有深度限制,如果遞歸層次太深,函數(shù)反復(fù)壓棧,棧內(nèi)存很快就溢出了
- 如果是有限次數(shù)的遞歸,可以使用遞歸調(diào)用,或者使用循環(huán)代替,循環(huán)代碼稍微復(fù)雜一點,但是只要不是死循環(huán),可以多次迭代直至算出結(jié)果
- 絕大多數(shù)遞歸,都可以使用循環(huán)實現(xiàn)
- 即使遞歸代碼間接,不建議使用遞歸
七.匿名函數(shù)
- 沒有函數(shù)名
- 借助lambda 表達(dá)式構(gòu)建匿名函數(shù)
- 格式:
lambda 參數(shù)列表:表達(dá)式 - 使用lambda 關(guān)鍵字來定義匿名函數(shù)
- 參數(shù)列表不需要小括號
- 冒號是用來分割參數(shù)列表和表達(dá)式的
- 不需要使用return ,表達(dá)式的值,就是匿名函數(shù)返回值
- lambda表達(dá)式(匿名函數(shù)) 只能寫在一行上,被稱為單行函數(shù)
- 用途:在高階函數(shù)傳參時,使用lamdba表達(dá)式,往往能簡化代碼。
print((lambda :0)())
print((lambda x,y=3:x+y)(5))
print((lambda x, y=3: x + y)(5, 6))
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
print((lambda *args: (x for x in args))(*range(5)))
print((lambda *args: [x+1 for x in args])(*range(5)))
print((lambda *args: {x+2 for x in args})(*range(5)))
[x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))]
[x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]
