【Python基礎知識庫】Python裝飾器的一些總結

以下是一些前人的總結參考:

python裝飾器

Python裝飾器各種類型詳解

Python各種類型裝飾器詳解說明

python裝飾器的4種類型

一文看懂Python系列之裝飾器

python裝飾器簡介

裝飾器本質上是一個函數,可以讓其他函數在不需要做任何代碼處理的前提下增加額外的功能,裝飾器的返回值也是一個函數對象(函數的引用)。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼到裝飾器中並繼續重用。概括的講,裝飾器的作用就是爲已經存在的對象添加額外的功能。

  • 裝飾器自身爲函數

(1). 被裝飾的對象爲函數,且不帶參數

import time
from functools import wraps

# 裝飾器爲函數,且不帶參數
def time_decorator(func):
    @wraps(func)  # 保證裝飾過的函數__name__屬性不變
    def inner():
        print("Hello inner")
        start = time.time()
        func()
        end = time.time()
        print('方法{}用時:{}秒'.format(func.__name__, end - start))

    return inner

# 被裝飾的對象爲函數,且不帶參數
@time_decorator
def foo():
    time.sleep(3)
    print("foo is running.")

# 調用裝飾後的foo函數
print(foo.__name__)
foo()

# 結果
'''
inner
Hello inner
foo is running.
方法foo用時:3.01444411277771秒
'''

在以上代碼中,@time_decorator裝飾器對foo函數進行了裝飾,這是一個不帶參數的裝飾器,當python解釋器執行到@time_decorator時,回去調用time_decorator函數,同時將被裝飾的對象的函數名foo作爲參數傳入,這時time_decorator函數接受到一個參數(即方法名foo),然後進入內嵌函數inner,計算開始時間,調用傳進來的foo方法,再計算結束時間,打印函數foo的耗時,最後將結果用內部函數inner返回,其實就是一個閉包函數。

首先打印的是foo.name,這裏是inner而不是foo,本質上是調用inner函數
其次打印的是inner的內容"Hello inner",然後開始調用foo函數,打印"func1 is running."
最後打印"方法foo用時:3.01444411277771秒"

(2). 被裝飾的對象爲函數,且帶參數

import time
from functools import wraps

# 裝飾器爲函數,且不帶參數
def time_decorator(func):
    """
    如果原函數有參數,那閉包函數必須保持參數個數一直,並且將參數傳遞給原方法
    """
    @wraps(func)  # 保證裝飾過的函數__name__屬性不變
    def inner(name):  # 如果被裝飾的函數有形參,那麼閉包函數必須有參數,且一致
        print("Hello inner")
        start = time.time()
        func(name)
        end = time.time()
        print('方法{}用時:{}秒'.format(func.__name__, end - start))

    return inner

# 調用裝飾後的foo函數,且帶參數
@time_decorator
def foo(name):
    time.sleep(3)
    print("hello " + name)

# 調用裝飾後的foo函數
print(foo.__name__)
foo('lucy')

# 結果
'''
foo
Hello inner
hello lucy
方法foo用時:3.000863790512085秒
'''

當被裝飾的函數,帶參數時,需要在裝飾器的閉包函數inner函數中添加一致的參數name,調用func對象時也需要加上一致的參數name,並且返回了以reurn inner形式返回閉包函數,具體調用過程看結果應該不難理解。

當然,如果被裝飾函數存在多個參數時,這裏使用了python中動態參數的概念,利用(*args, **kwargs)來接收可變參數和關鍵字參數,這樣裝飾器就可以支持任意的組合參數的函數了。裝飾器修改如下:

import time
from functools import wraps

# 裝飾器爲函數,且不帶參數
def time_decorator(func):
    @wraps(func)  # 保證裝飾過的函數__name__屬性不變
    def inner(*args, **kwargs):  # 接收可變參數和關鍵字參數
        print("Hello inner")
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print('方法{}用時:{}秒'.format(func.__name__, end - start))

    return inner

# 調用裝飾後的foo函數,且帶參數
@time_decorator
def foo(name):
    time.sleep(3)
    print("hello " + name)

# 調用裝飾後的foo函數
print(foo.__name__)
foo('lucy')

# 結果
'''
foo
Hello inner
hello lucy
方法foo用時:3.000807762145996秒
'''

(3). 被裝飾的對象爲函數,且帶返回值

import time
from functools import wraps

# 裝飾器爲函數,且不帶參數
def time_decorator(func):
    @wraps(func)  # 保證裝飾過的函數__name__屬性不變
    def inner():
        print("Hello inner")
        start = time.time()
        res = func()
        end = time.time()
        print('方法{}用時:{}秒'.format(func.__name__, end - start))
        return res

    return inner

# 被裝飾的對象爲函數,且帶返回值
@time_decorator
def foo():
    time.sleep(3)
    print("foo is running.")
    return "this is foo's return value"


# 調用裝飾後的foo函數
print(foo.__name__)
res = foo()
print('返回值:%s' % res)

# 結果
'''
foo
Hello inner
foo is running.
方法foo用時:3.000837802886963秒
返回值:this is foo's return value
'''

若被裝飾的函數是帶返回值的,閉包函數inner中,調用func()時必須相應的帶返回值,不然裝飾函數時,也不進行返回,默認爲None。

(4). 被裝飾的對象爲函數,且裝飾器帶參數也有返回值

import time
from functools import wraps

# 裝飾器爲函數,且帶參數帶返回值
def time_decorator(arg=None):  # 如果在調用裝飾器時爲給傳參數,則默認值爲None
    def wrapper(func):
        @wraps(func)  # 保證裝飾過的函數__name__屬性不變
        def inner(*args,**kwargs):
            print("Hello inner")
            print("裝飾器的參數爲:{}".format(arg))
            start = time.time()
            res = func(*args,**kwargs)
            end = time.time()
            print('方法{}用時:{}秒'.format(func.__name__, end - start))
            return res

        return inner
    return wrapper

# 被裝飾的對象爲函數,且不定參數
@time_decorator('hello')
def foo():
    time.sleep(3)
    print("foo is running.")
    return "this is foo's return value"

# 調用裝飾後的foo函數
print(foo.__name__)
res = foo()
print('返回值:%s' % res)

# 結果
'''
foo
Hello inner
裝飾器的參數爲:hello
foo is running.
方法foo用時:3.000739336013794秒
返回值:this is foo's return value
'''

帶有參數的裝飾器,需要寫三層嵌套函數,最外一層用來傳遞裝飾器的參數。上面的裝飾器即帶參數也帶返回值,先執行time_decorator(‘hello’),返回wrapper函數的應用,然後使用wrapper對函數foo進行裝飾,內層inner使用的是*args,**kwargs接收可變參數和關鍵字參數,具體運行順序看結果應該不難理解。

(5). 被裝飾的對象爲類,且不帶參數

from functools import wraps

# 裝飾器爲函數,且不帶參數
def singleton(cls):
    # 在裝飾器中聲明一個變量,用於保存類的實例,那麼這個實例對象將始終是通過一個實例對象
    instances = {}
    @wraps(cls)
    def inner():
        print('Hello inner')
        print('class name: {}'.format(cls.__name__))
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return inner

# 被裝飾的對象是類,且不帶參數
@singleton
class Foo():
    def __init__(self):
        self.name = 'lucy'

    def say(self):
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print(Foo.__name__)
foo = Foo()
foo.say()

# 結果
'''
Foo
Hello inner
class name: Foo
her name is lucy
'''

上面的例子是基於裝飾器的單例模式,通過裝飾器裝飾這個類,是的類在初始化時候始終將初始化實例賦值給instances,而instances是裝飾器的一個實例對象,通過實例賦值,instances始終佔有同一個內存空間,也就實現了單例模式設計。

當然,如果用這個裝飾器對類裏面方法say進行裝飾的話,此時裝飾器single接收到的參數cls=say,所以打印"class name"是say,因爲cls__name__就是say,這裏等同於函數裝飾器給函數進行裝飾,於是裝飾器的內層函數inner需要接受say帶來的參數name,不加參數則會報錯,其他與前面一樣。

from functools import wraps

# 裝飾器爲函數,且不帶參數
def singleton(cls):
    # 在裝飾器中聲明一個變量,用於保存類的實例,那麼這個實例對象將始終是通過一個實例對象
    instances = {}
    @wraps(cls)
    def inner(name):  # 必須和所修飾類裏面的函數參數個數一致,否則會報錯
        print('Hello inner')
        print('class name: {}'.format(cls.__name__))
        if cls not in instances:
            instances[cls] = cls(name)
        return instances[cls]
    return inner

# 被裝飾的對象是類中的函數,調用類中的初始化參數
class Foo():
    def __init__(self):
        self.name = 'lucy'

    @singleton
    def say(self):
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print(Foo.__name__)
foo = Foo()
foo.say()

# 結果
'''
Foo
Hello inner
class name: say
her name is lucy
'''

(6). 被裝飾的對象爲類,且帶參數

from functools import wraps

# 裝飾器爲函數,且不帶參數
def singleton(cls):
    # 在裝飾器中聲明一個變量,用於保存類的實例,那麼這個實例對象將始終是通過一個實例對象
    instances = {}

    @wraps(cls)
    def inner(*args, **kwargs):
        print('Hello inner')
        print('class name: {}'.format(cls.__name__))
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return inner

# 被裝飾的對象是類,且帶參數
@singleton
class Foo():
    def __init__(self, *args, **kwargs):
        self.id = args[0]
        self.name = kwargs.get('name_dict').get(self.id)

    def say(self):
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print(Foo.__name__)
foo = Foo('1', '2', '3', name_dict={'1': 'Lucy', '2': 'Linda', '3': 'Mary'})
foo.say()

# 結果
'''
Foo
Hello inner
class name: Foo
her name is Lucy

(7). 被裝飾的對象爲類,且裝飾器帶參數也有返回值

from functools import wraps

# 裝飾器爲函數,且帶參數帶返回值
def singleton(arg=None): # 如果在調用裝飾器時爲給傳參數,則默認值爲None
    def wrapper(cls):
        # 在裝飾器中聲明一個變量,用於保存類的實例,那麼這個實例對象將始終是通過一個實例對象
        instances = {}

        @wraps(cls)
        def inner(*args, **kwargs):
            print('Hello inner')
            print("裝飾器的參數爲:{}".format(arg))
            print('class name: {}'.format(cls.__name__))
            if cls not in instances:
                instances[cls] = cls(*args, **kwargs)
            return instances[cls]

        return inner
    return wrapper

# 被裝飾的對象是類,且帶參數
@singleton('hello')
class Foo():
    def __init__(self, *args, **kwargs):
        self.id = args[0]
        self.name = kwargs.get('name_dict').get(self.id)

    def say(self):
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print(Foo.__name__)
foo = Foo('1', '2', '3', name_dict={'1': 'Lucy', '2': 'Linda', '3': 'Mary'})
foo.say()

# 結果
'''
Foo
Hello inner
裝飾器的參數爲:hello
class name: Foo
her name is Lucy
'''

這裏與(4)類似,都是帶有參數的裝飾器,且裝飾器爲函數,需要寫三層嵌套函數,最外一層用來傳遞裝飾器的參數。上面的裝飾器即帶參數也帶返回值,先執行time_decorator(‘hello’),返回wrapper函數的應用,然後使用wrapper對函數foo進行裝飾,內層inner使用的是*args,**kwargs接收可變參數和關鍵字參數,具體運行順序看結果應該不難理解。

  • 裝飾器自身爲類

類裝飾器本質上和函數裝飾器原理、作用相同,都是爲其它函數增加額外的功能。但是相比於函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器可以直接依靠類內部的__call__方法來實現,當使用 @ 形式將類裝飾器附加到函數上時,就會調用類裝飾器的__call__方法。而不需要向函數裝飾器那樣,在裝飾器函數中定義嵌套函數,來實現裝飾功能。

(1). 被裝飾的對象爲函數,且不帶參數

import time
from functools import wraps

# 裝飾器是類,且不帶參數
class Decorator(object):
    def __init__(self,func):
        # 初始化函數只會調用一次,當第二次裝飾的時候,這一步就濾過了
        print('decorator init')
        print('func name is {}'.format(func.__name__))
        self.func = func

    def __call__(self):
        print('裝飾器中的功能:{} 睡眠3秒'.format(self.func.__name__))
        start = time.time()
        res = self.func()
        end = time.time()
        print('方法{}用時:{}秒'.format(self.func.__name__, end - start))
        return res

# 被裝飾的對象爲函數,且不帶參數
@Decorator
def foo():
    time.sleep(3)
    print("foo is running.")

# 調用裝飾後的foo函數
print(foo.func.__name__)
foo()

# 結果
'''
decorator init
func name is foo
foo
裝飾器中的功能:foo 睡眠3秒
foo is running.
方法foo用時:3.000671863555908秒
'''

裝飾器爲類時,調用__init__方法創建實例、傳遞參數,並調用__call__方法實現對被裝飾函數功能的添加。

(2). 被裝飾的對象爲函數,且帶參數

import time
from functools import wraps

# 裝飾器是類,且不帶參數
class Decorator(object):
    def __init__(self,func):
        # 初始化函數只會調用一次,當第二次裝飾的時候,這一步就濾過了
        print('decorator init')
        print('func name is {}'.format(func.__name__))
        self.func = func

    def __call__(self, name):
        print('裝飾器中的功能:{} 睡眠3秒'.format(self.func.__name__))
        start = time.time()
        res = self.func(name)
        end = time.time()
        print('方法{}用時:{}秒'.format(self.func.__name__, end - start))
        return res

# 被裝飾的對象爲函數,且帶參數
@Decorator
def foo(name):
    time.sleep(3)
    print("hello " + name)

# 調用裝飾後的foo函數
print(foo.func.__name__)
foo('lucy')

# 結果
'''
decorator init
func name is foo
foo
裝飾器中的功能:foo 睡眠3秒
hello lucy
方法foo用時:3.0009634494781494秒
'''

當被裝飾的函數,且帶參數時,需要在裝飾器類的__call__中添加一致的參數name,調用func對象時也需要加上一致的參數name,並且返回了以reurn inner形式返回閉包函數,具體調用過程看結果應該不難理解。

當然,如果被裝飾函數存在多個參數時,這裏使用了python中動態參數的概念,利用(*args, **kwargs)來接收可變參數和關鍵字參數,這樣裝飾器就可以支持任意的組合參數的函數了。裝飾器修改如下:

import time
from functools import wraps

# 裝飾器是類,且不帶參數
class Decorator(object):
    def __init__(self,func):
        # 初始化函數只會調用一次,當第二次裝飾的時候,這一步就濾過了
        print('decorator init')
        print('func name is {}'.format(func.__name__))
        self.func = func

    def __call__(self, *args, **kwargs):
        print('裝飾器中的功能:{} 睡眠3秒'.format(self.func.__name__))
        start = time.time()
        res = self.func(*args, **kwargs)
        end = time.time()
        print('方法{}用時:{}秒'.format(self.func.__name__, end - start))
        return res

# 被裝飾的對象爲函數,且帶參數
@Decorator
def foo(name):
    time.sleep(3)
    print("hello " + name)

# 調用裝飾後的foo函數
print(foo.func.__name__)
foo('lucy')

# 結果
'''
decorator init
func name is foo
foo
裝飾器中的功能:foo 睡眠3秒
hello lucy
方法foo用時:3.000622510910034秒
'''

(3). 被裝飾的對象爲函數,且帶返回值

import time
from functools import wraps

# 裝飾器是類,且不帶參數
class Decorator(object):
    def __init__(self,func):
        # 初始化函數只會調用一次,當第二次裝飾的時候,這一步就濾過了
        print('decorator init')
        print('func name is {}'.format(func.__name__))
        self.func = func

    def __call__(self):
        print('裝飾器中的功能:{} 睡眠3秒'.format(self.func.__name__))
        start = time.time()
        res = self.func()
        end = time.time()
        print('方法{}用時:{}秒'.format(self.func.__name__, end - start))
        return res

# 被裝飾的對象爲函數,且帶返回值
@Decorator
def foo():
    time.sleep(3)
    print("foo is running.")
    return "this is foo's return value"

# 調用裝飾後的foo函數
print(foo.func.__name__)
res = foo()
print('返回值:%s' % res)

# 結果
'''
decorator init
func name is foo
foo
裝飾器中的功能:foo 睡眠3秒
foo is running.
方法foo用時:3.0008039474487305秒
返回值:this is foo's return value
'''

和之前裝飾器是函數一樣,若被裝飾的函數是帶返回值的,閉包函數inner中,調用func()時必須相應的帶返回值,不然裝飾函數時,也不進行返回,默認爲None。

(4). 被裝飾的對象爲函數,且裝飾器帶參數也有返回值

import time
from functools import wraps

# 裝飾器是類,且帶參數帶返回值
class Decorator(object):
    def __init__(self, arg=None):  # 如果在調用裝飾器時爲給傳參數,則默認值爲None
        print('decorator init')
        self.arg = arg

    def __call__(self, func):
        @wraps(func)  # 保證裝飾過的函數__name__屬性不變
        def inner(*args, **kwargs):
            print("Hello inner")
            print('裝飾器的參數爲:{}'.format(self.arg))
            print('裝飾器中的功能:{} 睡眠3秒'.format(func.__name__))
            start = time.time()
            res = func(*args, **kwargs)
            end = time.time()
            print('方法{}用時:{}秒'.format(func.__name__, end - start))
            return res
        return inner

# 被裝飾的對象爲函數,且不帶參數
@Decorator('hello')
def foo():
    time.sleep(3)
    print("foo is running.")
    return "this is foo's return value"

# 調用裝飾後的foo函數
print(foo.__name__)
res = foo()
print('返回值:%s' % res)

# 結果
'''
decorator init
foo
Hello inner
裝飾器的參數爲:hello
裝飾器中的功能:foo 睡眠3秒
foo is running.
類foo用時:3.000250816345215秒
返回值:this is foo's return value
'''

和之前裝飾器是函數一樣,帶有參數的裝飾器類,會把被裝飾函數所帶的參數傳遞給裝飾器__init__進行初始化,而__call__作爲外層函數接收被裝飾函數的函數名,作爲參數傳遞給內層函數inner,這裏需要注意的是func傳入,不需要在前面加self了,其他的跟函數裝飾函數很相似,可以通過結果去理解整個裝飾器的運行過程。

(5). 被裝飾的對象爲類,且不帶參數

import time
from functools import wraps

# 裝飾器是類,且不帶參數
class Decorator(object):
    def __init__(self,func):
        # 初始化函數只會調用一次,當第二次裝飾的時候,這一步就濾過了
        print('decorator init')
        print('class name is {}'.format(func.__name__))
        self.func = func

    def __call__(self, *args, **kwargs):
        print('裝飾器中的功能:{} 睡眠3秒'.format(self.func.__name__))
        start = time.time()
        res = self.func(*args, **kwargs)
        end = time.time()
        print('類{}用時:{}秒'.format(self.func.__name__, end - start))
        return res

# 被裝飾的對象爲函數,且不帶參數
@Decorator
class Foo():
    def __init__(self):
        self.name = 'lucy'

    def say(self):
        time.sleep(3)
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print("類名:{}".format(Foo.func.__name__))
foo = Foo()
foo.say()

# 結果
'''
decorator init
func name is Foo
類名:Foo
裝飾器中的功能:Foo 睡眠3秒
類Foo用時:0.0秒
her name is lucy
'''

跟函數裝飾器裝飾類一樣,這裏把類Foo通過@Decorator類裝飾器傳遞給參數func,但這個類裝飾器沒有__get__方法,無法返回裝飾後的類,而是進行初始化。第二次調用,這時類Foo()就相當於調用了裝飾器的__call__方法,在裏面調用self.func()方法

(6). 被裝飾的對象爲類,且帶參數

import time
from functools import wraps

# 裝飾器是類,且不帶參數
class Decorator(object):
    def __init__(self,func):
        # 初始化函數只會調用一次,當第二次裝飾的時候,這一步就濾過了
        print('decorator init')
        print('class name is {}'.format(func.__name__))
        self.func = func

    def __call__(self, *args, **kwargs):
        print('裝飾器中的功能:{} 睡眠3秒'.format(self.func.__name__))
        start = time.time()
        res = self.func(*args, **kwargs)
        end = time.time()
        print('類{}用時:{}秒'.format(self.func.__name__, end - start))
        return res

# 被裝飾的對象爲類,且帶參數
@Decorator
class Foo():
    def __init__(self, *args, **kwargs):
        self.id = args[0]
        self.name = kwargs.get('name_dict').get(self.id)

    def say(self):
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print("類名:{}".format(Foo.func.__name__))
foo = Foo('1', '2', '3', name_dict={'1': 'Lucy', '2': 'Linda', '3': 'Mary'})
foo.say()

# 結果
'''
decorator init
class name is Foo
類名:Foo
裝飾器中的功能:Foo 睡眠3秒
方法Foo用時:0.0秒
her name is Lucy
'''

(7). 被裝飾的對象爲類,且裝飾器帶參數也有返回值

import time
from functools import wraps

# 裝飾器是類,且帶參數帶返回值
class Decorator(object):
    def __init__(self, arg=None):  # 如果在調用裝飾器時爲給傳參數,則默認值爲None
        print('decorator init')
        self.arg = arg

    def __call__(self, func):
        @wraps(func)  # 保證裝飾過的函數__name__屬性不變
        def inner(*args, **kwargs):
            print("Hello inner")
            print('裝飾器的參數爲:{}'.format(self.arg))
            print('裝飾器中的功能:{} 睡眠3秒'.format(func.__name__))
            start = time.time()
            res = func(*args, **kwargs)
            end = time.time()
            print('類{}用時:{}秒'.format(func.__name__, end - start))
            return res
        return inner

# 被裝飾的對象爲類,且帶參數
@Decorator('hello')
class Foo():
    def __init__(self, *args, **kwargs):
        self.id = args[0]
        self.name = kwargs.get('name_dict').get(self.id)

    def say(self):
        print("her name is {}".format(self.name))

# 實例化裝飾後的類
print("類名:{}".format(Foo.func.__name__))
foo = Foo('1', '2', '3', name_dict={'1': 'Lucy', '2': 'Linda', '3': 'Mary'})
foo.say()

# 結果
'''
decorator init
類名:Foo
Hello inner
裝飾器的參數爲:hello
裝飾器中的功能:Foo 睡眠3秒
類Foo用時:0.0秒
her name is Lucy
'''
  • 特殊的裝飾器(類靜態屬性裝飾器)
# 類靜態屬性裝飾器
class Foo(object):
    def __init__(self, height, weigth):
        self.height = height
        self.weigth = weigth

    @property
    def ratio(self):
        return self.height / self.weigth

foo = Foo(176, 120)
print(foo.ratio)

# 結果爲:1.4666666666666666

其中,@property是一個特殊的裝飾器,把ratio方法變成一個屬性,所以調用的時候是foo.ratio而不是foo.ratio()。這類特殊裝飾器需要python的特定的屬性和機制的支持纔可以實現,不同特性的裝飾器所需機制不同。

# 實現@property裝飾器效果

class Prop(object):
    def __init__(self, arg):
        self.arg = arg

    def __get__(self, instance, owner):
        return self.arg(instance)

# 使用效果與原生的@property裝飾器的一樣
class Foo(object):
    def __init__(self, height, weigth):
        self.height = height
        self.weigth = weigth

    @Prop
    def ratio(self):
        return self.height / self.weigth

foo = Foo(176, 120)
print(foo.ratio)

# 結果爲:1.4666666666666666

經典的裝飾器裝飾類,通過setattr魔術方法,對Person類進行修改,name作爲類屬性,name = TypeCheck(name,required_type),這樣修改了Person類,使得Person類有了兩個類變量,一個是name = TypeCheck('name', required_type),另一個是age = TypeCheck('age', required_type)

因此實例化時Person('lucy', 18),self.name中name不是實例變量而是類變量,會調用描述器TypeCheck,賦值的時候,就會調用__set__方法,取值的時候會調用__get__方法。

# 經典的裝飾器裝飾類
from functools import wraps

class TypeCheck:
    def __init__(self, srcType, dstType):
        self.srcType = srcType
        self.dstType = dstType

    # instance == a, cls == A
    def __get__(self, instance, cls):
        if instance is None:
            return self
        return instance.__dict__[self.srcType]

    def __set__(self, instance, value):
        if isinstance(value, self, dstType):
            instance.__dict__[self.srcType] = value
        else:
            raise TypeError('{} should be {}'.format(value, self.dstType))

# 裝飾器自身是一個函數
def type_assert(**kwargs):
    def dec(cls):
        def wraps(*args):
            for name, required_type in kwargs.items():
                setattr(cls, name, TypeCheck(name, required_type))
            return cls(*args)  # 這裏是實例化新的Person類後返回實例對象,也就是p
        return wraps
    return dec

# 裝飾對象是一個類,且帶參數
@type_assert(name=str, age=int)
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 實例化新的Person類,這裏相對於調用的是wraps函數
p = Person('lucy', 18)
print(p.name)
print(p.age)

# 裝飾器修改後的Person類爲下面這個新的Person類,因此實例化Person的時候,調用的是下面這個新的Person
class Person:
    name = TypeCheck('name', str)
    age = TypeCheck('age', int)

    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age
  • 裝飾器順序

當有兩個或兩個以上裝飾器裝飾一個函數時,那麼語法糖語句執行流程的順序是從下往上(就近原則,靠近函數定義的先執行)。

# 第一個裝飾器wrapper1
def wrapper1(func):
    print("wapper1")

    def inner():
        print("inner1")
        return '<b>' + func() + '</b>'

    return inner

# 第二個裝飾器wrapper2
def wrapper2(func):
    print("wapper2")

    def inner():
        print("inner2")
        return '<i>' + func() + '</i>'

    return inner

# 兩個裝飾器
@wrapper1
@wrapper2
def foo():
    print("foo")
    print("end")
    return "hello"

# 調用裝飾後的foo函數
res = foo()
print(res)

# 結果
'''
wapper2
wapper1
inner1
inner2
foo
end
<b><i>hello</i></b>
'''

根據結果,函數foo先用wapper2裝飾器進行裝飾,接着是用wrapper1再進行裝飾,但是在調用過程中又是先執行第一個裝飾器wrapper1,然後在執行第二個裝飾器wrapper2。

具體分析其過程,就近原則先用第二個裝飾器wrapper2進行裝飾,@wrapper2等價於foo = wrapper2(foo),此時括號內的foo即是函數名,而外部的foo實際指向的是wrapper2的inner。
@wrapper1等價於foo = wrapper1(foo),此時括號內的foo指向的是wrapper2.inner,而外部的foo指向wrapper1的inner。

當執行到@wrapper1時要對下面的函數進行裝飾,此時解釋器繼續往下走,發現並不是一個函數名,而又是一個裝飾器,這時@wrapper1裝飾器暫停執行,而接着執行接下來的裝飾器@wrapper2,接着把foo函數名傳入到裝飾器wrapper2函數func,從而打印"wrapper2",在wrapper2裝飾完後,此時的foo指向wrapper2的inner函數地址,這是又返回來執行@wrapper1,接着把新的foo(即wrapper2.inner)傳入wrapper裝飾器函數中,因此打印"wrapper1",在wrapper1裝飾完後,此時的foo指向wrapper1的inner函數地址。

在調用foo函數的時候,根據上述分析,此時foo指向wrapper1的inner函數地址,故打印"inner1",接下來調用func()的時候,實際上調用的是wrapper2.inner()函數,所以會打印"inner2",而wrapper2.inner()函數中,調用的func()纔是最初傳入的foo函數,所以打印"foo"和"end",最後一層層調用完後打印的"<b><i>hello</i></b>"。

  • 通用萬能裝飾
from functools import wraps

def decorator_all(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("萬能裝飾器")
        return func(*args, **kwargs)

    return wrapper

才疏學淺,如有問題,歡迎指正,謝謝!

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