1. 程式人生 > >python函式、高階函式、裝飾器、引數註解、快取

python函式、高階函式、裝飾器、引數註解、快取

函式

1. 函式的定義

由若干語句組成的語句塊、函式名稱、引數列表構成,它是組織程式碼的最小單元。

完成一定的功能。

函式也是物件,python把函式的預設值放在了屬性中,這個屬性就伴隨著這個函式物件的整個生命週期。

2. 函式的作用

  • 結構化程式設計是對程式碼的最基本的<font color = red >封裝</font>,一般按照功能組織一段程式碼。
  • 封裝的目的是為了<font color = red >複用</font>,減少冗餘程式碼。
  • 程式碼更加簡潔美觀,可讀易懂。

3. 函式的分類:

內建函式;庫函式;自建函式

4. 函式的定義、呼叫

定義

def語句定義函式

def 函式名(引數列表):

函式體(程式碼塊)

[return 返回值]

定義中的引數列表成為形式引數,只是一種符號表達,簡稱形參

定義需要在呼叫前,否則會丟擲NameError異常。


呼叫

函式定義,只是聲明瞭一個函式,它不會被指執行,需要呼叫。

呼叫的方式,就是函式名加上小括號,括號內寫上引數。

呼叫時寫的引數時實際引數,是實實在在傳入的值,簡稱實參

傳參時位置引數要放在關鍵字引數前面。

引數傳遞:不可變型別,傳遞副本給函式,函式內操作不影響原始值

       可變型別,傳遞的是地址引用,函式內操作可能影響原始值

定義形參和傳遞實參時候的注意事項
  1. 引數呼叫時傳入的引數要和定義的個數相匹配,可變引數例外
  2. 定義時,預設引數要放在非預設引數前。
  3. 定義時加* :可變位置引數:可以收集位置引數傳入的所有引數,收集多個實參為一個tuple。可變位置引數不能用關鍵字傳參。
  4. 形參加**:可變關鍵字引數,只能用關鍵字傳參。可變關鍵字引數,收集的實參名稱和值組成一個字典,所以可修改。
  5. 函式名也是識別符號,返回值也是值,函式是可呼叫的物件,callable(函式名) -> True。
  6. 混合使用引數的時候,可變引數要放到引數列表的最後,普通引數要放到引數列表的最前面,可變位置引數發要放在可變關鍵字引數的前面。
  7. keyword-only引數:如果在一個可變位置引數後面,出現了普通引數,此時這個普通引數已經變成了一個keyword-only引數
  8. 引數列表引數一般順序是,普通引數、預設引數、可變位置引數、keyword-only引數(可帶預設值)、可變關鍵字引數。
  9. 引數解構:
    • 給函式提供實參的時候,可以在集合型別前使用*或者**,把集合型別的結構解開,提取出所有元素作為函式的實參。
    • 非字典型別使用*解構成位置引數
    • 字典型別使用**解構成關鍵字引數
    • 提取出來的元素數目要和引數的要求匹配,也要和引數的型別匹配。

5.函式的返回值

python函式使用return語句返回“返回值”。

所有函式都有返回值。如果沒有return語句,隱式呼叫return None。

return語句並不一定是函式的語句塊的最後一條語句

return語句只能執行一次,執行完,函式結束,當前return後面的語句就不會再運行了。所以函式一次只能返回一個值,不能返回多個值,但是可以返回容器,容器裡面包含多個值。(return [1,3,5]是指明返回一個列表,是一個列表物件;return 1,3,5看似返回多個值,隱式的被python封裝成一個元組)

作用:結束函式呼叫、返回值。

函式的巢狀

函式有可見範圍。這就是作用域的概念

外層變數作用域在內層作用域可見

內部函式不能在外部直接使用,會拋NameError異常,因為它不可見。

6. 作用域

一個識別符號的可見範圍,這就是識別符號的作用域。一般常說的是變數的作用域。

全域性作用域:在整個函式執行環境中都可見。

區域性作用域:在函式、類內部可見;區域性變數的使用範圍不能超過其所在的區域性作用域。

例子:

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是全域性的變數,雖然能在內部函式foo中可見,但是在foo函式內部出現了 a = ,出現等號就是即賦值即重新定義,那麼=的右邊作為賦值的內容 :a+1,但在函式中,此時的a已經算是重新定義了一個區域性變數,而不是用外面的全域性變數,但是此時a還沒有完成賦值就被拿來進行加1操作,所以才會報錯。

解決辦法:

在這條語句前增加x=0之類的賦值語句,或者使用global 告訴內部作

用域,去全域性作用域查詢變數定義

預設值的作用域

函式名.__defaults__屬性:使用元組來儲存所有位置引數預設值,它不會因為在函式體中使用了它而發生了變化。

函式名.__kwdefaults__屬性:使用字典儲存所有keyword-only引數的預設值。

使用可變型別(引用引數)作為預設值,就有可能修改這個預設值。

使用按需修改,例子。

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__)

# 函式體內,不改變預設值
# 使用影子拷貝建立一個新的物件,永遠不能改變傳入的引數
# xyz都是傳入引數或者預設引數的副本,如果就想修改原引數,無能為力

def foo(xyz=None, u='abc', z=123):
    if xyz is None:
        xyz = []
    xyz.append(1) 
    print(xyz)

# 使用不可變型別預設值
# 如果使用預設值None就建立一個列表
# 如果傳入一個列表,就修改這個列表


全域性變數global

使用global關鍵字的變數,將函式內的定義的區域性變數宣告成全域性變數。

如果函式需要使用外部全域性變數,請使用函式的形參傳參解決。

儘量不使用

nonlocal關鍵字

nonlocal將變數標記為不再本地作用域定義,而在<font color =red>上一級的某一級</font>區域性作用域中定義,但不能是全域性作用域中定義。

7.<font color = blue>閉包</font>

自由變數:未在本地作用域中定義的變數,例如定義在內層函式外的外層函式的作用域中的變數

<font color = red >閉包</font>:是一概念,是巢狀函式中,指的是在內層函式中引用到外層函式的自由變數,就形成了閉包。

8.變數名解析原則LEGB

  • Local,本地作用域、區域性作用域的local名稱空間。函式呼叫時建立,呼叫結束消亡。

  • Enclosing,Python2.2時引入了巢狀函式,實現了閉包,這個就是巢狀函式的外部函式的名稱空間。

  • Global,全域性作用域,即一個模組的名稱空間。模組被import時建立,直譯器退出時消亡。

  • Build-in,內建模組的名稱空間,生命週期從python直譯器啟動時建立到直譯器退出時消亡。例如 print(open),print和open都是內建的變數。

函式變數作用域:級別:Built_in(內建) > Global(全域性) > Enclosing(封裝)> local(本地)

9.函式的銷燬

全域性函式銷燬
  1. 重新定義同名函式
  2. del 語句刪除函式物件名稱,函式物件的引用計數減1
  3. 程式結束時
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__)

區域性函式銷燬
  1. 重新在上級作用域定義同名函式
  2. del 語句刪除函式名稱,函式物件的引用計數減1
  3. 上級作用域銷燬時
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.遞迴函式

函式是需要壓棧的,棧和執行緒相關。

11.匿名函式

沒有名字的函式,python藉助lamdba表示式構建匿名函式。

引數列表不需要小括號。

冒號是用來區分引數列表和表示式的。

不需要return,表示式的值,就是匿名函式返回值。

lambda表示式(匿名函式)只能寫在一行上,被成為單行函式。

用途:在高階函式傳參時,使用lambda表示式,往往能簡化程式碼

格式:lambda 引數列表:表示式

   lambda x :  x**2

   (lambda x :  x**2) ()  #呼叫

12.高階函式

參時是一個函式,或者輸出一個函式

13.裝飾器

裝飾器本質上是一個 Python 函式或類。

它可以讓其他函式或類在不需要做任何程式碼修改的前提下增加額外功能,裝飾器的返回值也是一個函式/類物件。它經常用於有切面需求的場景,比如:插入日誌、效能測試、事務處理、快取、許可權校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,我們就可以抽離出大量與函式功能本身無關的雷同程式碼到裝飾器中並繼續重用。概括的講,裝飾器的作用就是為已經存在的物件新增額外的功能。

多裝飾器的函式執行順序,由底向上

14.引數註解

文件註解:函式內的最前面,使用三個雙引號

函式註解:

  • python3.5引入
  • 對函式的引數進行型別註解
  • 對函式的返回值進行型別註解
  • 只對函式引數做一個輔助的說明,並不對函式引數進行型別檢查
  • 提供給第三方工具,做程式碼分析,發現隱藏的bug
  • 函式註解的資訊,儲存在__annotations__屬性中

變數註解:python3.6引入

函式引數型別檢查

思路:

  • 函式引數的檢查,一定是在函式外
  • 函式應該作為引數,傳入到檢查函式中
  • 檢查函式拿到函式傳入的實際引數,與形參宣告對比
  • __annotations__屬性是一個字典,其中包括返回值型別的宣告,加入要位置引數的判斷,無法和字典中的宣告對應,使用inspect模組

inspect模組:提取獲取物件資訊的函式,可以檢查函式和類、型別檢查

  • inspect.isfunction(add) , 是否是函式
  • inspect.ismethod(add) , 是否是類的方法
  • inspect.isgenerator(add) , 是否是生成器物件
  • inspect.isgeneratorfunction(add) , 是否是生成器函式
  • inspect.isclass(add) , 是否是類
  • inspect.ismodule(inspect) , 是否是模組
  • inspect.isbuiltin(print) , 是否是內建物件

signature(callable),獲取簽名(函式簽名包含了一個函式的資訊,包括函式名,它的引數型別,它的所在的類和名稱空間及其他資訊)

Parameter物件

儲存在元組中

輸入屬性:inspect.signature.parameters.annotation/name/kind/default

返回屬性:inspect.signature.return_annotation

當不知道該方法下面有多少屬性的時候,可以先用type檢視該它的型別,然後通過匯入模組,使用引數註解的方式來檢視。