Python中的函數裝飾器和閉包

導語:本文章記錄了本人在學習Python基礎之函數篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:python

一、掌握裝飾器的本質、功能和特色;
二、瞭解閉包的概念以及Python變量調用規則;
三、瞭解並學會使用標準庫中重要的裝飾器;
四、掌握參數化裝飾器的意義和代碼實現方式。

1、裝飾器基礎知識

裝飾器功能(decorator):將被裝飾的函數看成參數傳遞給與裝飾器對應的函數(名稱相同的函數),並返回包裝後的被裝飾的函數。
裝飾器本質:是一個返回函數的高階函數。
裝飾器特色:緩存

一、多數裝飾器會把被裝飾的函數替換成其餘函數
二、函數裝飾器在導入模塊時當即執行,而被裝飾的函數只在明確調用時執行。

裝飾器有時採用嵌套函數表示的緣由(我的理解):
一些裝飾器的裝飾功能只有在被裝飾函數被調用時方可觸發,所以須要用嵌套函數的形式來編寫。閉包

2、自由變量、閉包與nonlocal聲明

自由變量(free variable):指未在本地做用域綁定的變量,介於局部變量和全局變量之間。
變量查找規則:在python中, 一個變量的查找順序是 LEGB (L:Local 局部環境,E:Enclosing 閉包,G:Global 全局,B:Built-in 內建).函數

閉包:引用了自由變量的函數。學習

閉包的做用:ui

  • 閉包的最大特色是能夠將父函數的變量與內部函數綁定,並返回綁定變量後的函數,此時即使生成閉包的環境(父函數)已經釋放,閉包仍然存在。
  • 這個過程很像類(父函數)生成實例(閉包),不一樣的是父函數只在調用時執行,執行完畢後其環境就會釋放,而類則在文件執行時建立,通常程序執行完畢後做用域才釋放。
  • 所以對一些須要重用的功能且不足以定義爲類的行爲,使用閉包會比使用類佔用更少的資源,且更輕巧靈活。

nonlocal聲明:能夠將局部變量聲明爲自由變量。spa

eg:計算移動平均值的高階函數:code

def averager():
    sum=0
    n=0
    def avg(i):
        nonlocal sum,n
        sum+=i
        n+=1
        return print(sum/n)
    return avg  
a=averager()
a(3)
a(5)
a(7)

輸出分別是3,4,5orm

3、裝飾器進階使用

1.標準庫中的裝飾器

Python內置了三個用於裝飾方法的函數:property,classmethod和staticmethod
三個重要的內置裝飾器:對象

  • functools.wraps:
    (1)協助構建行爲良好的裝飾器。
    (2)能夠把被裝飾對象的相關屬性複製到裝飾器中,默認有 __module__、__name__、__doc__。
    (3)我的理解,裝飾器在實現裝飾的過程當中意見把被裝飾函數替換了。此時你想調用被裝飾函數的__doc__和__name__會發現是none,此時經過functools.wraps就能夠在裝飾時把相關屬性複製過來使用,避免這個問題發生。
  • functools.lru_cache
    (1)實現備忘功能,即緩存,避免發生重複調用來提升效率。
    (2)注意調用時必須帶括號,由於此裝飾器包含maxsize和typed兩個參數,帶括號表示使用默認參數。不然 functools.lru_cache不清楚該如何執行。
  • functools.singledispatch
    爲函數提供重載功能。被其裝飾的函數會成爲泛函數(generic function):根據第一個參數的類型,用不一樣的方式執行相同操做的一組函數。

以functools.lru_cache爲例實現斐波那契函數的計時裝飾器:.

import time
import functools
def clock(func):
    @functools.lru_cache()#減小重複自引用,避免重複計算
    def clocked(arg):
        t0=time.perf_counter()
        func(arg)
        result=func(arg)
        t1=time.perf_counter()
        processtime=t1-t0
        name=func.__name__
        print("[{0:.8f}] {1}({2})={3}".format(processtime,name,arg,result))
        return result#很是重要,不然破壞原fibs函數致使沒法遞歸調用。
    return clocked
 
@clock
def fibs(n):
    if n<2:
        return 1
    else:
        return fibs(n-1)+fibs(n-2)
fibs(6)#輸出8

2.疊放裝飾器

如同函數能夠嵌套使用,裝飾器亦能夠疊放起來裝飾同一對象。語法以下:
@d1
@d2
def f1():
pass
上述裝飾操做等價於d1(d2(f)),故易知疊放在上端的裝飾器靠後執行。

3.參數化裝飾器

普通裝飾器的進階版,經過參數接口能夠更方便的定製咱們須要的裝飾器,適應需求變化。
代碼實現較普通裝飾器多嵌套一層函數,用來傳遞用戶輸入的參數。以計時器舉例形式以下:

import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT): #三層函數來實現
    def decorate(func): 
        def clocked(*_args): 
            t0 = time.time()
            _result = func(*_args) 
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args) 
            result = repr(_result) 
            print(fmt.format(**locals())) 
            return _result 
        return clocked 
    return decorate 
if __name__ == '__main__':
    @clock()
    def snooze(seconds):
        time.sleep(seconds)
    for i in range(3):
        snooze(.123)

輸出:

[0.12480044s] snooze(0.123) -> None
[0.13660240s] snooze(0.123) -> None
[0.12480044s] snooze(0.123) -> None

4.類裝飾器

上邊實現參數化裝飾器的代碼因爲包含三重嵌套略顯複雜, 事實上覆雜的裝飾器用 class 實現更方便。經過 __call__ 方法便可改寫參數化裝飾器。具體實現留做勤勞的你去課後思考,感興趣的能夠與我私信交流。

相關文章
相關標籤/搜索