Python 裝飾器-進階

帶參數的裝飾器

裝飾器是一個函數(可以稱爲裝飾器函數),他的參數就是被裝飾的函數(可以被稱爲基礎函數),函數體是定義一個內部函數(可以被稱爲包裝器函數),在包裝器函數中調用基礎函數並添加“裝飾”功能,並返回基礎函數的執行結果。最後裝飾器函數以包裝器函數爲返回值。 調用裝飾器返回包裝器函數(這時基礎函數並未被調用)。將返回的包裝器函數賦值給基礎函數同名變量,再執行與基礎函數名同名的變量(這個變量指向包裝器函數,可以被調用)從而執行裝飾功能和基礎函數的功能。

python中這個裝飾器實現語法是不能改動的,爲了引入額外自定義的參數,需要把 “裝飾器”封裝起來:即在外層函數中定義“裝飾器函數”,最後外層函數返回裝飾器函數, 而裝飾器函數仍然依上述規定,而額外的參數可以通過外部函數引入,示例如下:

def out_decorator(arg1, arg2):
    print('step1')
    def decorator(decorated_func):
        print('step3')
        def wrapper(*args,**kw):
            print('step5')
            outcome = decorated_func(*args,**kw)
            print('step6')
            return outcome
        print('step4')
        return wrapper
    print('step2')
    return decorator

可以如下方式使用這個帶參數的裝飾器

@out_decorator(1,'car')
def decorated_func(*args,**kw):
    print(args)

很多文章說經過裝飾的decorated_func('hello') 的調用相當於:out_decorator(1, 'car')(decorated_func)('hello'),但實際的執行還是有差異的。比較上述兩種函數的執行過程,

decorated_func('hello')
print('----------')
out_decorator(1, 'car')(decorated_func)('hello')

從step1 到step6 如上述代碼中的打印語句所示。

step1
step2
step3
step4
step5
('hello',)
step6
----------
step1
step2
step3
step4
step5
step5
('hello',)
step6
step6

step5 和step6分別被執行了兩次。

 

 

裝飾器與__name__

調用完成之後,新的問題又出現了:函數的__name__屬性因爲使用裝飾器改變了。比如 提交打印語句print(decorated_func.__name__) 。我們期待結果是decorated_func 但結果顯示wrapper。這是因爲經過修飾器的修飾後我們實際執行的是修飾器函數。

解決方法一:

python爲了解決這個問題提供了一裝飾器:@functools.wraps(fun),這個裝飾器的作用就是將被修飾的函數的__name__屬性改爲指定的名稱,用這個裝飾器修飾我們的自定義裝飾器函數(是不是很有趣?)。如下所示:(記得先導入functools包)

 import functools

def out_decorator(arg1, arg2):
    print('step1')    
    def decorator(decorated_func):
        print('step3')
        
        @functools.wraps(decorated_func)
        def wrapper(*args,**kw):
            print('step5')
            outcome = decorated_func(*args,**kw)
            print('step6')
            return outcome
        print('step4')
        return wrapper
    print('step2')
    return decorator

解決方法二:

既然我們最後實際執行的是包裝器函數,我們把在返回包裝器函數前把包裝器函數的__name__的值更新爲基礎函數的__name__值即可。

def out_decorator(arg1, arg2):
    print('step1')    
    def decorator(decorated_func):
        print('step3')
        def wrapper(*args,**kw):
            print('step5')
            outcome = decorated_func(*args,**kw)
            print('step6')
            return outcome
        print('step4')
        wrapper.__name__=decorated_func.__name__
        return wrapper
    print('step2')
    return decorator

@out_decorator(1,'car')
def decorated_func(*args,**kw):
    print(args)

print(decorated_func.__name__)

 

類裝飾器

我們知道裝飾器是一個函數,python中函數是一個可被調用的對象,類和類對象也可以被調用:當將類名當作函數形式調用時其實是調用了類的__init__方法返回一個類對象變量;當將類對象變量名當作函數形式調用時調用了類對象的__call__方法。這與裝飾器的概念很像,因此我們可以通過 __init__和__call__函數實現裝飾器效果。如下所示:

 

不帶參數的類裝飾器

被基礎函數當作__init__參數傳入,在__call__函數中添加裝飾功能並調用基礎函數。類裝飾器同樣會有__name__改變的問題,不帶參數的類裝飾器裝飾後的函數本質是指向了類裝飾器的一個對象,因此__name__返回的是類對象的__name__屬性,因此爲不帶參數的類裝飾器的類添加設置實例屬性__name__爲基礎函數的的__name__值即可。

class decorator_cls():
    def __init__(self, decorated_f):
        self.decorated_f= decorated_f
        self.__name__=decorated_f.__name__
    def __call__(self,*arg,**kw):
        print("step1")
        self.decorated_f(*arg,**kw)
        print("step2")

@decorator_cls        
def hello(*arg, **kw):
    print(arg)
    
hello('world')

 

帶參數的類裝飾器

裝飾器的參數是從__init__函數傳入,基礎函數則當作__call__函數的參數傳入,並在__call__函數中定義並返回包裝器函數。帶參數的類裝飾器裝時後的基礎函數實際是指向了__call__函數的返回的包裝器函數,因此對此包裝器函數用@functools.wraps(decorated_f)裝飾器進行裝飾即可重新恢復__name__的值。也可以用上述的方法二,爲包裝器函數的__name__重新賦值。

import functools
class decorator_cls():
    def __init__(self, arg1,arg2):
        self.arg1= arg1
        self.arg2=arg2
        
    def __call__(self,decorated_f):
        @functools.wraps(decorated_f)  #方法一
        def wrapper(*arg,**kw):
            print("step1")
            decorated_f(*arg,**kw)
            print("step2")

        wrapper.__name__=decorated_func.__name__  #方法二
        return wrapper
    
@decorator_cls('d','c')       
def hello(*arg, **kw):
    print(arg)
    
@decorator_cls('d','c')       
def hello(*arg, **kw):
    print(arg)
    
hello('world')

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章