標籤:學習筆記
函數
- 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, 5x, y, z = showlist() # 使用解構提取更為方便
函數嵌套
- 在一個函數中定義了另外一個函數
- 函數有可見範圍,這就是範圍的概念
- 內建函式不能在外部直接使用,會拋NameError異常,因為它不可見
範圍
全域範圍
局部範圍
- 在函數、類等內部可見
- 局部變數使用範圍不能超過其所在的局部範圍
- 外層變數範圍在內層範圍可見
內層範圍inner中,如果定義了o=97,相當於當前範圍中重新定義了一個新的變數o,但是這個o並沒有覆蓋外層範圍outer中的o
- 全域變數global
#x = 5def foo():global xx = 10x += 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()
遞迴的效能
- 迴圈稍微複雜一些,但是只要不是死迴圈,可以多次迭代直至算出結果
- 遞迴有深度限制,如果遞迴複雜,函數反覆壓棧,占記憶體很快就溢出了
間接遞迴
- 通過別的函數調用了函數自身
- 但是,如果構成了迴圈遞迴調用是非常危險的,但是往往在代碼複雜的情況下,還是可能發生這種調用。要用代碼的規範來避免這種遞迴調用的發生
- 總結
- 遞迴是一種很自然地表達,符合邏輯思維
- 遞迴相對運行效率低,每一次調用函數都要開闢棧幀
- 遞迴有深度限制,如果遞迴層次太深,函數反覆壓棧,棧記憶體很快就溢出了
- 如果是有限次數的遞迴,可以使用遞迴,或者使用迴圈代替,迴圈代碼稍微複雜一些,但是只要不是死迴圈,可以多次迭代直至算出結果
- 絕大多數遞迴,都可以使用迴圈實現
- 即使遞迴代碼很簡潔,但是能不用則不用遞迴
匿名函數
[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)