Python 裝飾器【轉】

來源:互聯網
上載者:User

標籤:設計   markdown   來源   tor   strong   變數   就會   直接   text   

相關

函數範圍LEGB:L>E>G>B

  • L:local函數內部範圍
  • E:enclosing函數內部與內嵌函數之間(閉包)
  • G:global全域範圍
  • B:build-in內建範圍

閉包Closure概念:內建函式中隊enclosing範圍的變數進行引用,將該變數放到Closure的屬性中,當內建函式處理時,可以直接使用該變數。

函數實質與屬性

  1. 函數是一個對象
  2. 函數執行完成後內部變數回收
  3. 函數屬性
  4. 函數傳回值

閉包作用:1.封裝 2.代碼複用

裝飾器

裝飾器其實就是閉包所帶來的一個文法糖

裝飾器

由於函數也是一個對象,而且函數對象可以被賦值給變數,所以,通過變數也能調用該函數。

>>> from time import ctime>>> def now():...     return ctime()... >>> f = now>>> f()'Sat Feb 24 16:43:28 2018'

函數對象有一個__name__屬性,可以拿到函數的名字:

>>> now.__name__'now'>>> f.__name__'now'

現在,假設我們要增強now()函數的功能,比如,在函數調用前後自動列印日誌,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。

本質上,decorator就是一個返回函數的高階函數。所以,我們要定義一個能列印日誌的decorator,可以定義如下:

>>> def log(func):...     def wrapper(*args, **kw):...         print('call %s():' % func.__name__)...         return func(*args, **kw)...     return wrapper

觀察上面的log,因為它是一個decorator,所以接受一個函數作為參數,並返回一個函數。我們要藉助Python的@文法,把decorator置於函數的定義處:

@logdef now():    print '2013-12-25'

調用now()函數,不僅會運行now()函數本身,還會在運行now()函數前列印一行日誌:

>>> @log... def now():...     print(ctime())...     ... >>> now()call now():Sat Feb 24 16:47:53 2018

@log放到now()函數的定義處,相當於執行了語句:

now = log(now)

由於log()是一個decorator,返回一個函數,所以,原來的now()函數仍然存在,只是現在同名的now變數指向了新的函數,於是調用now()將執行新函數,即在log()函數中返回的wrapper()函數。

wrapper()函數的參數定義是(*args, **kw),因此,wrapper()函數可以接受任意參數的調用。在wrapper()函數內,首先列印日誌,再緊接著調用原始函數。

如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更複雜。比如,要自訂log的文本:

def log(text):    def decorator(func):        def wrapper(*args, **kw):            print '%s %s():' % (text, func.__name__)            return func(*args, **kw)        return wrapper    return decorator

這個3層嵌套的decorator用法如下:

@log('execute')def now():    print '2013-12-25'

執行結果如下:

>>> now()execute now():2013-12-25

和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:

>>> now = log('execute')(now)

我們來剖析上面的語句,首先執行log(‘execute‘),返回的是decorator函數,再調用返回的函數,參數是now函數,傳回值最終是wrapper函數。

以上兩種decorator的定義都沒有問題,但還差最後一步。因為我們講了函數也是對象,它有__name__等屬性,但你去看經過decorator裝飾之後的函數,它們的__name__已經從原來的‘now‘變成了‘wrapper‘

>>> now.__name__'wrapper'

因為返回的那個wrapper()函數名字就是‘wrapper‘,所以,需要把原始函數的__name__等屬性複製到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。

不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內建的functools.wraps就是幹這個事的,所以,一個完整的decorator的寫法如下:

import functoolsdef log(func):    @functools.wraps(func)    def wrapper(*args, **kw):        print 'call %s():' % func.__name__        return func(*args, **kw)    return wrapper

或者針對帶參數的decorator:

import functoolsdef log(text):    def decorator(func):        @functools.wraps(func)        def wrapper(*args, **kw):            print '%s %s():' % (text, func.__name__)            return func(*args, **kw)        return wrapper    return decorator

import functools是匯入functools模組。模組的概念稍候講解。現在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。

在物件導向(OOP)的設計模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現,而Python除了能支援OOP的decorator外,直接從文法層次支援decorator。Python的decorator可以用函數實現,也可以用類實現。

decorator可以增強函數的功能,定義起來雖然有點複雜,但使用起來非常靈活和方便。

請編寫一個decorator,能在函數調用的前後列印出‘begin call‘‘end call‘的日誌。

再思考一下能否寫出一個@log的decorator,使它既支援:

@logdef f():    pass

又支援:

@log('execute')def f():    pass
偏函數

Python的functools模組提供了很多有用的功能,其中一個就是偏函數(Partial function)。要注意,這裡的偏函數和數學意義上的偏函數不一樣。

在介紹函數參數的時候,我們講到,通過設定參數的預設值,可以降低函數調用的難度。而偏函數也可以做到這一點。舉例如下:

int()函數可以把字串轉換為整數,當僅傳入字串時,int()函數預設按十進位轉換:

>>> int('12345')12345

int()函數還提供額外的base參數,預設值為10。如果傳入base參數,就可以做N進位的轉換:

>>> int('12345', base=8)5349>>> int('12345', 16)74565

假設要轉換大量的二進位字串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2()的函數,預設把base=2傳進去:

def int2(x, base=2):    return int(x, base)

這樣,我們轉換二進位就非常方便了:

>>> int2('1000000')64>>> int2('1010101')85

functools.partial就是協助我們建立一個偏函數的,不需要我們自己定義int2(),可以直接使用下面的代碼建立一個新的函數int2

>>> import functools>>> int2 = functools.partial(int, base=2)>>> int2('1000000')64>>> int2('1010101')85

所以,簡單總結functools.partial的作用就是,把一個函數的某些參數給固定住(也就是設定預設值),返回一個新的函數,調用這個新函數會更簡單。

注意到上面的新的int2函數,僅僅是把base參數重新設定預設值為2,但也可以在函數調用時傳入其他值:

>>> int2('1000000', base=10)1000000

最後,建立偏函數時,實際上可以接收函數對象、*args**kw這3個參數,當傳入:

int2 = functools.partial(int, base=2)

實際上固定了int()函數的關鍵字參數base,也就是:

int2('10010')

相當於:

kw = { base: 2 }int('10010', **kw)

當傳入:

max2 = functools.partial(max, 10)

實際上會把10作為*args的一部分自動加到左邊,也就是:

max2(5, 6, 7)

相當於:

args = (10, 5, 6, 7)max(*args)

結果為10

參考

來源於慕課網教程筆記

Python 裝飾器【轉】

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.