Python第四周 學習筆記(1)
阿新 • • 發佈:2018-04-15
學習筆記函數
- Python的函數沒有return語句,隱式會返回一個None值
- 函數是可調用的對象,callable()
函數參數
- 參數調用時傳入的參數要和定義的個數相匹配(可變參數例外)
- 位置參數
- def f(x, y, z) 調用使用 f(1, 3, 5)
- 按照參數定義順序傳入實參
- 關鍵字參數
- def f(x, y, z) 調用使用 f(x=1, y=3, z=5)
- 使用形參的名字來出入實參的方式,如果使用了形參名字,那麽傳參順序就可和定義順序不同
- 傳參
- 要求位置參數必須在關鍵字參數之前傳入,位置參數是按位置對應的
函數參數默認值
- 定義時,在形參後跟上一個值
- 作用
- 參數的默認值可以在未傳入足夠的實參的時候,對沒有給定的參數賦值為默認值
- 參數非常多的時候,並不需要用戶每次都輸入所有的參數,簡化函數調用
可變參數
-
一個形參可以匹配任意個參數
- 在形參前使用*表示該形參是可變參數,可以接收多個實參
-
收集多個實參為一個tuple
-
關鍵字參數的可變參數
- 形參前使用**符號,表示可以接收多個關鍵字參數
- 收集的實參名稱和值組成一個字典
- 有位置可變參數和關鍵字可變參數
- 位置可變參數在形參前使用一個星號*
- 關鍵字可變參數在形參前使用兩個星號**
- 位置可變參數和關鍵字可變參數都可以收集若幹個實參,位置可變參數收集形成一個tuple,關鍵字可變參數收集形成一個dict
- 混合使用參數的時候,可變參數要放到參數列表的最後,普通參數需要放到參數列表前面,位置可變參數需要在關鍵字可變參數之前
keyword-only參數
- 如果在一個星號參數後,或者一個位置可變參數後,出現的普通參數,實際上已經不是普通的參數了,而是keyword-only參數
def fn(*args, x):
print(x)
print(args)
def(**kwargs, x):
print(x)
print(kwargs)
直接報語法錯誤
可以理解為kwargs會截獲所有的關鍵字參數,就算你寫了x=5,x也永遠得不到這個值,所以語法錯誤
- keyword-only 參數另一種形式
def fn(*, x,y): print(x,y) fn(x=5,y=6)
*號之後,普通形參都變成了必須給出的keyword-only 參數
可變參數和參數默認值
def fn(*args, x=5):
print(x)
print(args)
參數規則
- 參數列表參數一般順序是,普通參數、缺省參數、可變位置參數、keyword-only參數(可帶缺省值)、可變關鍵字參數
參數解構
- 給函數提供實參的時候,可以在集合類型前使用*或者**,把集合類型的結構解開,提取出所有元素作為函數的實參
- 非字典類型使用*解構成位置參數
- 字典類型使用**解構成關鍵字參數
-
提取出來的元素數目要和參數的要求匹配,也要和參數的類型匹配
- 參數解構和可變參數
- 給函數提供實參的時候,可以在集合類型前使用*或者**,把集合類型的結構解開,提取出所有元素作為函數的實參
函數返回值與作用域
函數的返回值
- Python函數使用return語句返回“返回值”
- 所有函數都有返回值,如果沒有return語句,隱式調用return None
- return 語句並不一定是函數的語句塊的最後一條語句
- 一個函數可以存在多個return語句,但是只有一條可以被執行。如果沒有一條return語句被執行到,隱式調用return None
- 如果有必要,可以顯示調用return None,可以簡寫為return
- 如果函數執行了return語句,函數就會返回,當前被執行的return語句之後的其它語句就不會被執行了
-
作用:結束函數調用、返回值
- 返回多個值
- 函數不能同時返回多個值
- return [1, 3, 5] 是指明返回一個列表,是一個列表對象
- return 1, 3, 5 看似返回多個值,隱式的被python封裝成了一個元組
def showlist(): return 1, 3, 5 x, y, z = showlist() # 使用解構提取更為方便
函數嵌套
- 在一個函數中定義了另外一個函數
- 函數有可見範圍,這就是作用域的概念
- 內部函數不能在外部直接使用,會拋NameError異常,因為它不可見
作用域
-
全局作用域
- 在整個程序運行環境中都可見
-
局部作用域
- 在函數、類等內部可見
- 局部變量使用範圍不能超過其所在的局部作用域
- 外層變量作用域在內層作用域可見
-
內層作用域inner中,如果定義了o=97,相當於當前作用域中重新定義了一個新的變量o,但是這個o並沒有覆蓋外層作用域outer中的o
- 全局變量global
#x = 5 def foo(): global x x = 10 x += 1 # 報錯嗎? print(x) # 打印什麽? print(x) #打印什麽?
- 使用global關鍵字的變量,將foo內的x聲明為使用外部的全局作用域中定義的x
-
但是,x = 10 賦值即定義,在內部作用域為一個外部作用域的變量x賦值,不是在內部作用域定義一個新變量,所以x+=1不會報錯。註意,這裏x的作用域還是全局的
- x+=1這種是特殊形式產生的錯誤的原因?先引用後賦值,而python動態語言是賦值才算定義,才能被引用。解決辦法,在這條語句前增加x=0之類的賦值語句,或者使用global 告訴內部作用域,去全局作用域查找變量定義
-
內部作用域使用x = 5之類的賦值語句會重新定義局部作用域使用的變量x,但是,一旦這個作用域中使用global聲明x為全局的,那麽x=5相當於在為全局作用域的變量x賦值
- global使用原則
- 外部作用域變量會內部作用域可見,但也不要在這個內部的局部作用域中直接使用,因為函數的目的就是為了封裝,盡量與外界隔離
- 如果函數需要使用外部全局變量,請使用函數的形參傳參解決
- 一句話:不用global。學習它就是為了深入理解變量作用域
閉包(重要概念)
- 自由變量:未在本地作用域中定義的變量。例如定義在內存函數外的外層函數的作用域中的變量
- 閉包:就是一個概念,出現在嵌套函數中,指的是內層函數引用到了外層函數的自由變量,就形成了閉包。很多語言都有這個概念,最熟悉就是JavaScript
- 使用global可以解決,但是這使用的是全局變量,而不是閉包
- 如果要對普通變量的閉包,Python3中可以使用nonlocal
nonlocal關鍵字
- 使用了nonlocal關鍵字,將變量標記為不在本地作用域定義,而在上級的某一級局部作用域中定義,但不能是全局作用域中定義
默認值的作用域
-
函數也是對象,python把函數的默認值放在了屬性中,這個屬性就伴隨著這個函數對象的整個生命周期
- 屬性defaults中使用元組保存所有位置參數默認值,它不會因為在函數體內使用了它而發生改變
-
屬性kwdefaults中使用字典保存所有keyword-only參數的默認值
- 使用可變類型作為默認值,就可能修改這個默認值
-
有時候這個特性是好的,有的時候這種特性是不好的,有副作用
- 第一種方法
- 使用影子拷貝創建一個新的對象,永遠不能改變傳入的參數
def foo(xyz=[], u=‘abc‘, z=123): xyz = xyz[:] # 影子拷貝 xyz.append(1) print(xyz) foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10,5]) print(foo.__defaults__)
- 使用影子拷貝創建一個新的對象,永遠不能改變傳入的參數
函數體內,不改變默認值
xyz都是傳入參數或者默認參數的副本,如果就想修改原參數,無能為力
- 第二種方法
- 通過值的判斷就可以靈活的選擇創建或者修改傳入對象
- 這種方式靈活,應用廣泛
- 很多函數的定義,都可以看到使用None這個不可變的值作為默認參數,可以說這是一種慣用法
def foo(xyz=None, u=‘abc‘, z=123):
if xyz is None:
xyz = []
xyz.append(1)
print(xyz)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
使用不可變類型默認值
如果使用缺省值None就創建一個列表
如果傳入一個列表,就修改這個列表
變量名解析原則LEGB
- Local,本地作用域、局部作用域的local命名空間。函數調用時創建,調用結束消亡
- Enclosing,Python2.2時引入了嵌套函數,實現了閉包,這個就是嵌套函數的外部函數的命名空間
- Global,全局作用域,即一個模塊的命名空間。模塊被import時創建,解釋器退出時消亡
- Build-in,內置模塊的命名空間,生命周期從python解釋器啟動時創建到解釋器退出時消亡。例如 print(open),print和open都是內置的變量
- 所以一個名詞的查找順序就是LEGB
函數的銷毀
-
全局函數銷毀
- 重新定義同名函數
- del 語句刪除函數對象
- 程序結束時
- 局部函數銷毀
- 重新在上級作用域定義同名函數
- del 語句刪除函數名稱,函數對象的引用計數減1
- 上級作用域銷毀時
遞歸
- 函數直接或者間接調用自身就是遞歸
- 遞歸需要有邊界條件、遞歸前進段、遞歸返回段
- 遞歸一定要有邊界條件
- 當邊界條件不滿足的時候,遞歸前進
-
當邊界條件滿足的時候,遞歸返回
-
遞歸要求
- 遞歸一定要有推出條件,遞歸調用一定要執行這個退出條件。沒有退出條件的遞歸調用,就是無限調用
- 遞歸調用的深度不宜過深
- Python對遞歸調用的深度做了限制以保護解釋器
- 超過遞歸深度限制,拋出RecursionError maxinum recursion depth exceeded 超出最大深度sys.getrecursionlimit()
-
遞歸的性能
- 循環稍微復雜一些,但是只要不是死循環,可以多次叠代直至算出結果
- 遞歸有深度限制,如果遞歸復雜,函數反復壓棧,占內存很快就溢出了
-
間接遞歸
- 通過別的函數調用了函數自身
- 但是,如果構成了循環遞歸調用是非常危險的,但是往往在代碼復雜的情況下,還是可能發生這種調用。要用代碼的規範來避免這種遞歸調用的發生
- 總結
- 遞歸是一種很自然地表達,符合邏輯思維
- 遞歸相對運行效率低,每一次調用函數都要開辟棧幀
- 遞歸有深度限制,如果遞歸層次太深,函數反復壓棧,棧內存很快就溢出了
- 如果是有限次數的遞歸,可以使用遞歸,或者使用循環代替,循環代碼稍微復雜一些,但是只要不是死循環,可以多次叠代直至算出結果
- 絕大多數遞歸,都可以使用循環實現
- 即使遞歸代碼很簡潔,但是能不用則不用遞歸
匿名函數
- 使用Lambda表達式構建匿名函數
-
格式
- lambda 參數列表:表達式
- 使用lambda關鍵字來定義匿名函數
- 參數列表不需要小括號
- 冒號是用來分割參數列表和表達式的
- 不需要使用return,表達式的值,就是匿名函數返回值
-
lambda表達式(匿名函數)只能寫在一行上,被稱為單行函數
- 用途
- 在高階函數傳參時,使用lambda表達式,往往能簡化代碼
[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))]
Python第四周 學習筆記(1)