函數(shù)
1. 函數(shù)的定義
由若干語句組成的語句塊、函數(shù)名稱、參數(shù)列表構成,它是組織代碼的最小單元。
完成一定的功能。
函數(shù)也是對象,python把函數(shù)的默認值放在了屬性中,這個屬性就伴隨著這個函數(shù)對象的整個生命周期。
2. 函數(shù)的作用
- 結構化編程是對代碼的最基本的<font color = red >封裝</font>,一般按照功能組織一段代碼。
- 封裝的目的是為了<font color = red >復用</font>,減少冗余代碼。
- 代碼更加簡潔美觀,可讀易懂。
3. 函數(shù)的分類:
內(nèi)建函數(shù);庫函數(shù);自建函數(shù)
4. 函數(shù)的定義、調用
定義
def語句定義函數(shù)
def 函數(shù)名(參數(shù)列表):
函數(shù)體(代碼塊)
[return 返回值]
定義中的參數(shù)列表成為形式參數(shù),只是一種符號表達,簡稱形參。
定義需要在調用前,否則會拋出NameError異常。
調用
函數(shù)定義,只是聲明了一個函數(shù),它不會被指執(zhí)行,需要調用。
調用的方式,就是函數(shù)名加上小括號,括號內(nèi)寫上參數(shù)。
調用時寫的參數(shù)時實際參數(shù),是實實在在傳入的值,簡稱實參
傳參時位置參數(shù)要放在關鍵字參數(shù)前面。
參數(shù)傳遞:不可變類型,傳遞副本給函數(shù),函數(shù)內(nèi)操作不影響原始值
可變類型,傳遞的是地址引用,函數(shù)內(nèi)操作可能影響原始值
定義形參和傳遞實參時候的注意事項
- 參數(shù)調用時傳入的參數(shù)要和定義的個數(shù)相匹配,可變參數(shù)例外
- 定義時,缺省參數(shù)要放在非缺省參數(shù)前。
- 定義時加* :可變位置參數(shù):可以收集位置參數(shù)傳入的所有參數(shù),收集多個實參為一個tuple??勺兾恢脜?shù)不能用關鍵字傳參。
- 形參加**:可變關鍵字參數(shù),只能用關鍵字傳參??勺冴P鍵字參數(shù),收集的實參名稱和值組成一個字典,所以可修改。
- 函數(shù)名也是標識符,返回值也是值,函數(shù)是可調用的對象,callable(函數(shù)名) -> True。
- 混合使用參數(shù)的時候,可變參數(shù)要放到參數(shù)列表的最后,普通參數(shù)要放到參數(shù)列表的最前面,可變位置參數(shù)發(fā)要放在可變關鍵字參數(shù)的前面。
- keyword-only參數(shù):如果在一個可變位置參數(shù)后面,出現(xiàn)了普通參數(shù),此時這個普通參數(shù)已經(jīng)變成了一個keyword-only參數(shù)
- 參數(shù)列表參數(shù)一般順序是,普通參數(shù)、缺省參數(shù)、可變位置參數(shù)、keyword-only參數(shù)(可帶缺省值)、可變關鍵字參數(shù)。
- 參數(shù)解構:
- 給函數(shù)提供實參的時候,可以在集合類型前使用*或者**,把集合類型的結構解開,提取出所有元素作為函數(shù)的實參。
- 非字典類型使用*解構成位置參數(shù)
- 字典類型使用**解構成關鍵字參數(shù)
- 提取出來的元素數(shù)目要和參數(shù)的要求匹配,也要和參數(shù)的類型匹配。
5.函數(shù)的返回值
python函數(shù)使用return語句返回“返回值”。
所有函數(shù)都有返回值。如果沒有return語句,隱式調用return None。
return語句并不一定是函數(shù)的語句塊的最后一條語句
return語句只能執(zhí)行一次,執(zhí)行完,函數(shù)結束,當前return后面的語句就不會再運行了。所以函數(shù)一次只能返回一個值,不能返回多個值,但是可以返回容器,容器里面包含多個值。(return [1,3,5]是指明返回一個列表,是一個列表對象;return 1,3,5看似返回多個值,隱式的被python封裝成一個元組)
作用:結束函數(shù)調用、返回值。
函數(shù)的嵌套
函數(shù)有可見范圍。這就是作用域的概念
外層變量作用域在內(nèi)層作用域可見
內(nèi)部函數(shù)不能在外部直接使用,會拋NameError異常,因為它不可見。
6. 作用域
一個標識符的可見范圍,這就是標識符的作用域。一般常說的是變量的作用域。
全局作用域:在整個函數(shù)運行環(huán)境中都可見。
局部作用域:在函數(shù)、類內(nèi)部可見;局部變量的使用范圍不能超過其所在的局部作用域。
例子:
a = 5
def foo():
a += 1
foo()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
UnboundLocalError: local variable 'a' referenced before assignment
報錯原因:
a += 1其實就是a = a + 1,a = 5是全局的變量,雖然能在內(nèi)部函數(shù)foo中可見,但是在foo函數(shù)內(nèi)部出現(xiàn)了 a = ,出現(xiàn)等號就是即賦值即重新定義,那么=的右邊作為賦值的內(nèi)容 :a+1,但在函數(shù)中,此時的a已經(jīng)算是重新定義了一個局部變量,而不是用外面的全局變量,但是此時a還沒有完成賦值就被拿來進行加1操作,所以才會報錯。
解決辦法:
在這條語句前增加x=0之類的賦值語句,或者使用global 告訴內(nèi)部作
用域,去全局作用域查找變量定義
默認值的作用域
函數(shù)名.__defaults__屬性:使用元組來保存所有位置參數(shù)默認值,它不會因為在函數(shù)體中使用了它而發(fā)生了變化。
函數(shù)名.__kwdefaults__屬性:使用字典保存所有keyword-only參數(shù)的默認值。
使用可變類型(引用參數(shù))作為默認值,就有可能修改這個默認值。
使用按需修改,例子。
def foo(xyz=[], u='abc', z=123):
xyz = xyz[:] # 影子拷貝
xyz.append(1)
print(xyz)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
# 函數(shù)體內(nèi),不改變默認值
# 使用影子拷貝創(chuàng)建一個新的對象,永遠不能改變傳入的參數(shù)
# xyz都是傳入?yún)?shù)或者默認參數(shù)的副本,如果就想修改原參數(shù),無能為力
def foo(xyz=None, u='abc', z=123):
if xyz is None:
xyz = []
xyz.append(1)
print(xyz)
# 使用不可變類型默認值
# 如果使用缺省值None就創(chuàng)建一個列表
# 如果傳入一個列表,就修改這個列表
全局變量global
使用global關鍵字的變量,將函數(shù)內(nèi)的定義的局部變量聲明成全局變量。
如果函數(shù)需要使用外部全局變量,請使用函數(shù)的形參傳參解決。
盡量不使用
nonlocal關鍵字
nonlocal將變量標記為不再本地作用域定義,而在<font color =red>上一級的某一級</font>局部作用域中定義,但不能是全局作用域中定義。
7.<font color = blue>閉包</font>
自由變量:未在本地作用域中定義的變量,例如定義在內(nèi)層函數(shù)外的外層函數(shù)的作用域中的變量
<font color = red >閉包</font>:是一概念,是嵌套函數(shù)中,指的是在內(nèi)層函數(shù)中引用到外層函數(shù)的自由變量,就形成了閉包。
8.變量名解析原則LEGB
Local,本地作用域、局部作用域的local命名空間。函數(shù)調用時創(chuàng)建,調用結束消亡。
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)置的變量。
函數(shù)變量作用域:級別:Built_in(內(nèi)建) > Global(全局) > Enclosing(封裝)> local(本地)
9.函數(shù)的銷毀
全局函數(shù)銷毀
- 重新定義同名函數(shù)
- del 語句刪除函數(shù)對象名稱,函數(shù)對象的引用計數(shù)減1
- 程序結束時
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
return xyz
print(foo(), id(foo), foo.__defaults__)
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
return xyz
print(foo(), id(foo), foo.__defaults__)
del foo
print(foo(), id(foo), foo.__defaults__)
局部函數(shù)銷毀
- 重新在上級作用域定義同名函數(shù)
- del 語句刪除函數(shù)名稱,函數(shù)對象的引用計數(shù)減1
- 上級作用域銷毀時
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
def inner(a=10):
pass
print(inner)
def inner(a=100):
print(xyz)
print(inner)
return inner
bar = foo()
print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
del bar
print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
10.遞歸函數(shù)
函數(shù)是需要壓棧的,棧和線程相關。
11.匿名函數(shù)
沒有名字的函數(shù),python借助lamdba表達式構建匿名函數(shù)。
參數(shù)列表不需要小括號。
冒號是用來區(qū)分參數(shù)列表和表達式的。
不需要return,表達式的值,就是匿名函數(shù)返回值。
lambda表達式(匿名函數(shù))只能寫在一行上,被成為單行函數(shù)。
用途:在高階函數(shù)傳參時,使用lambda表達式,往往能簡化代碼
格式:lambda 參數(shù)列表:表達式
lambda x : x**2
(lambda x : x**2) () #調用
12.高階函數(shù)
參時是一個函數(shù),或者輸出一個函數(shù)
13.裝飾器
裝飾器本質上是一個 Python 函數(shù)或類。
它可以讓其他函數(shù)或類在不需要做任何代碼修改的前提下增加額外功能,裝飾器的返回值也是一個函數(shù)/類對象。它經(jīng)常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,我們就可以抽離出大量與函數(shù)功能本身無關的雷同代碼到裝飾器中并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。
多裝飾器的函數(shù)執(zhí)行順序,由底向上
14.參數(shù)注解
文檔注解:函數(shù)內(nèi)的最前面,使用三個雙引號
函數(shù)注解:
- python3.5引入
- 對函數(shù)的參數(shù)進行類型注解
- 對函數(shù)的返回值進行類型注解
- 只對函數(shù)參數(shù)做一個輔助的說明,并不對函數(shù)參數(shù)進行類型檢查
- 提供給第三方工具,做代碼分析,發(fā)現(xiàn)隱藏的bug
- 函數(shù)注解的信息,保存在__annotations__屬性中
變量注解:python3.6引入
函數(shù)參數(shù)類型檢查
思路:
- 函數(shù)參數(shù)的檢查,一定是在函數(shù)外
- 函數(shù)應該作為參數(shù),傳入到檢查函數(shù)中
- 檢查函數(shù)拿到函數(shù)傳入的實際參數(shù),與形參聲明對比
- __annotations__屬性是一個字典,其中包括返回值類型的聲明,加入要位置參數(shù)的判斷,無法和字典中的聲明對應,使用inspect模塊
inspect模塊:提取獲取對象信息的函數(shù),可以檢查函數(shù)和類、類型檢查
- inspect.isfunction(add) , 是否是函數(shù)
- inspect.ismethod(add) , 是否是類的方法
- inspect.isgenerator(add) , 是否是生成器對象
- inspect.isgeneratorfunction(add) , 是否是生成器函數(shù)
- inspect.isclass(add) , 是否是類
- inspect.ismodule(inspect) , 是否是模塊
- inspect.isbuiltin(print) , 是否是內(nèi)建對象
signature(callable),獲取簽名(函數(shù)簽名包含了一個函數(shù)的信息,包括函數(shù)名,它的參數(shù)類型,它的所在的類和名稱空間及其他信息)
Parameter對象
保存在元組中
輸入屬性:inspect.signature.parameters.annotation/name/kind/default
返回屬性:inspect.signature.return_annotation
當不知道該方法下面有多少屬性的時候,可以先用type查看該它的類型,然后通過導入模塊,使用參數(shù)注解的方式來查看。