python裝飾器入門探究

歪文發表於2019-03-01

簡介

裝飾器是可呼叫的物件,其引數是另一個函式(被裝飾的函式)。 python的世界中,一切皆物件,把函式看做一個物件,一個可以拼接、編輯的物件。執行物件的方法是__ 物件()__

裝飾器只是python的語法糖

觀察下面的兩個呼叫,他們的效果是一樣的。

#!/usr/bin/env python
#-*-coding:utf-8-*-

'''
@file: dec4.py
@time: 2018/12/13 17:15
'''
import functools

def register(func):        
    @functools.wraps(func)
    def warpper(*args,**kwargs):
        print("啦啦啦,這裡裝飾函式 %s"%func.__name__)
        result = func()      #這裡就是真正的執行邏輯
        return result
    return warpper       # 返回包裝後的函式

@register
def f1():
    print("使用@語法的裝飾器 %s"%f1.__name__)


def f2():
    print("沒@語法的裝飾器 %s" % f2.__name__)

if __name__ == '__main__':
    f1()
    register(f2)()
    #或者
    # f_ = register(f2)
    # f_()
複製程式碼

image.png | center | 572x147

裝飾器啟動裝飾的時機

1、檔案匯入時裝飾器已經啟動對函式的裝飾。

裝飾器的一個關鍵特性是,它們在被裝飾的函式定義之後立即執行。 這通常是在匯入時(即 Python 載入模組時)。--cookbook

建立檔案 dec1.py

# dec1.py

registry = []  # 存放註冊函式

def register(func):
    print('裝飾器入列表(%s)' % func.__name__)
    registry.append(func)
    return func

@register
def f1():
    print('執行 %s'%f1.__name__)

@register
def f2():
    print('執行 %s' % f2.__name__)
複製程式碼

建立檔案 dec2.py

# dec2.py
from 你的目錄 import dec1
複製程式碼

執行檔案 dec2.py 輸出如下:

image.png | center | 539x87

裝飾器返回的結果

1、返回目標函式。 2、返回目標函式的結果。

以上 register的用法適用於對函式進行預處理的場景,例如flask框架的路由註冊,在flask app啟動時將所有的函式收集起來,與註冊的路徑進行一一對應,但是函式執行時跟原函式沒有任何區別,因為裝飾器返回的還是目標函式。假如我們需要在函式執行時進行日誌的輸出,則需要將函式進行一層封裝,即將目標函式包裝成另一個函式,這時返回的是另一個函式,只是包裝後的函式可以進行額外的操作。

以下是兩個裝飾器的寫法,注意區別。


import functools
def register1(func):      #在檔案引入時返回目標函式的裝飾器
    #這裡進行一些函式預處理的操作,例如將函式名字收集進一個列表
    #list.append(func.__name__)
    return func

def register2(func):     #在函式執行時返回函式的裝飾器
    @functools.wraps(func)
    def warpper(*args,**kwargs):
        print("函式沒有執行 %s"%func.__name__)
        result = func      #這裡就是真正的執行邏輯      #注意-----這裡沒有執行函式
        return result
    return warpper       # 返回包裝後的函式


def register3(func):     #返回目標函式結果的裝飾器
    @functools.wraps(func)
    def warpper(*args,**kwargs):

        print("函式開始執行 %s"%func.__name__)
        result = func()      #這裡就是真正的執行邏輯
        return result
    return warpper       # 返回包裝後的函式

@register1
def f1():
    print("執行函式 %s"%f1.__name__)

@register2
def f2():
    print("執行函式 %s"%f2.__name__)

@register3
def f3():
    print("執行函式 %s"%f3.__name__)


if __name__ == '__main__':
    f1()
    f2()
    f3()
複製程式碼

執行結果:

image.png | center | 547x165

這時新增程式碼:

if __name__ == '__main__':
    f1()
    f2()
    f_ = f2()
    print("開始真正執行f2函式啦")
    f_()
    f3()
複製程式碼

執行結果:

image.png | center | 575x225

裝飾函式的屬性保持

1、使用@functools.wraps

你會發現下面的程式碼中,如果wrapper函式沒有使用@functools.wraps裝飾的話,f1.__name__返回的是wrapper函式的名字,這是因為我們返回的函式已經是wrapper這個函式,所以__name__屬性也就不是原來函式的了,@functools.wraps裝飾器的作用就是將目標函式的屬性例如__name__等原封不動轉移給包裝好的函式。

#!/usr/bin/env python
#-*-coding:utf-8-*-

'''
@file: dec4.py
@time: 2018/12/13 17:15
'''
import functools

def register(func):     #返回目標函式結果的裝飾器

    def warpper(*args,**kwargs):
        result = func()      #這裡就是真正的執行邏輯
        return result
    return warpper       # 返回包裝後的函式

def register2(func):     #返回目標函式結果的裝飾器
    @functools.wraps(func)
    def warpper(*args,**kwargs):
       
        result = func()      #這裡就是真正的執行邏輯
        return result
    return warpper       # 返回包裝後的函式

@register
def f1():
    print("函式名為 %s"%f1.__name__)

@register2
def f2():
    print("函式名為 %s" % f2.__name__)


if __name__ == '__main__':
    f1()
    f2()
    #或者
    #f_ = register(f2)
    #f_()
複製程式碼

執行結果:

python裝飾器入門探究

帶引數的裝飾器

1、外層巢狀一層函式傳參,返回一個裝飾器。

裝飾器也是一個物件,只要使用一個函式封裝裝飾器,返回目標裝飾器即可,這時就可以通過外層的函式傳進引數。

#!/usr/bin/env python
# -*-coding:utf-8-*-

'''
@file: dec5.py
@time: 2018/12/13 18:13
'''
import functools


def dec_arg(arg=None):

    def register(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('傳進的引數為 %s' % arg)
            return func(*args, **kwargs)
        return wrapper
    return register  #這是裝飾器

@dec_arg(arg='aaaa')
def f1():
    print("執行 %s"%f1.__name__)

def f2():
    print("執行 %s" % f2.__name__)


if __name__ == '__main__':
    f1()

    f2_ = dec_arg(arg='bbbb')(f2)
    f2_()

    dec_arg(arg='cccc')(f2)()
複製程式碼

image.png | left | 652x240

相關文章