Python進階: 通過實例詳解裝飾器

Latrice2344 8年前發布 | 9K 次閱讀 Python 測試技術 Python開發

Python中的裝飾器有很多用處,比如輸出日志、參數檢查、代理設置、計數計時、結果緩存等等。本文就通過幾個裝飾器例子,詳細解釋一下Python中裝飾器的用法。

  • 一步步從簡到繁學習裝飾器用法

  • 其他一些裝飾器實例

  • Python中自帶的裝飾器

一步步從簡到繁學習裝飾器用法

(1)最簡單的裝飾器,實現日志輸出功能:

# 構建裝飾器
def logging(func):
    @functools.wraps(func)
    def decorator():
        print("%s called" % func.__name__)
        result = func()
        print("%s end" % func.__name__)
        return result
    return decorator

# 使用裝飾器
@logging
def test01():
    return 1

# 測試用例
print(test01())
print(test01.__name__)

代碼很簡單,很容易看懂。這里注意"functools.wraps"用法,其目的是"test01.__name__"輸出正確的"test01"。"@logging"相當于"test01 = logging(test01)",返回的是decorator函數,所以如果不加"functools.wraps",則"test01.__name__"返回為"decorator"。

注意,此時test01沒有參數,對于帶有參數的函數,logging裝飾器則不再適用。那么如果想裝飾帶有參數的函數,裝飾器該怎么寫呢?

(2)裝飾器傳入函數參數,并正確返回結果:

# 構建裝飾器
def logging(func):
    @functools.wraps(func)
    def decorator(a, b):
        print("%s called" % func.__name__)
        result = func(a, b)
        print("%s end" % func.__name__)
        return result
    return decorator

# 使用裝飾器
@logging
def test01(a, b):
    print("in function test01, a=%s, b=%s" % (a, b))
    return 1

# 測試用例
print(test01(1, 2))

這里的test01函數帶有參數a、b,那么decorator函數帶有同樣的參數即可。那么問題又來了,如何讓logging裝飾器更加通用,而不是只裝飾參數為兩個的函數呢?這時候自然想到Python中的 * 和 ** 的用法。

# 構建裝飾器
def logging(func):
    @functools.wraps(func)
    def decorator(*args, **kwargs):
        print("%s called" % func.__name__)
        result = func(*args, **kwargs)
        print("%s end" % func.__name__)
        return result
    return decorator

# 使用裝飾器
@logging
def test01(a, b):
    print("in function test01, a=%s, b=%s" % (a, b))
    return 1

# 使用裝飾器
@logging
def test02(a, b, c=1):
    print("in function test02, a=%s, b=%s, c=%s" % (a, b, c))
    return 1

# 測試用例
print(test01(1, 2))
print(test02(1, 2, c=3, d=4))

此時對于任意參數的函數,logging都可以進行裝飾。但是注意,logging裝飾器是不帶參數的,那么裝飾器可以帶參數嗎?當然可以,我們換個例子:參數檢查。

(3)構建帶有參數的裝飾器,并正確返回結果:

# 構建裝飾器
def params_chack(a_type, b_type):
    def _outer(func):
        @functools.wraps(func)
        def _inner(a, b):
            assert isinstance(a, a_type) and isinstance(b, b_type)
            return func(a, b)
        return _inner
    return _outer

# 使用裝飾器
@params_chack(int, (list, tuple))
def test03(a, b):
    print("in function test03, a=%s, b=%s" % (a, b))
    return 1

# 測試用例
print(test03(1, [2, 3]))   # 參數正確
print(test03(1, 2))        # 參數錯誤

從代碼可以看出,實際上就是在原有裝飾器的基礎上,外層又加了一層包裝。params_check裝飾器的作用是限制第一個參數為a_type,第二個參數為b_type。類似于(2),這里如何讓裝飾器更加通用,而不是只裝飾參數為兩個的函數呢?這里又一次想到Python中的 * 和 **。

# 構建裝飾器
def params_chack(*types, **kwtypes):
    def _outer(func):
        @functools.wraps(func)
        def _inner(*args, **kwargs):
            result = [isinstance(_param, _type) for _param, _type in zip(args, types)]
            assert all(result), "params_chack: invalid parameters"
            result = [isinstance(kwargs[_param], kwtypes[_param]) for _param in kwargs if _param in kwtypes]
            assert all(result), "params_chack: invalid parameters"
            return func(*args, **kwargs)
        return _inner
    return _outer

# 使用裝飾器
@params_chack(int, str, c=(int, str))
def test04(a, b, c):
    print("in function test04, a=%s, b=%s, c=%s" % (a, b, c))
    return 1

# 測試用例
print(test04(1, "str", 1))         # 參數正確
print(test04(1, "str", "abc"))     # 參數正確
print(test04("str", 1, "abc"))     # 參數錯誤

此時params_check裝飾器不但能夠傳入任意個數的參數,而且支持K-V形式的參數傳遞。

(4)使用裝飾器裝飾類中的函數,比較簡單,直接看代碼。注意此時第一個參數為self本身:

# 使用裝飾器
class ATest(object):
    @params_chack(object, int, str)
    def test(self, a, b):
        print("in function test of ATest, a=%s, b=%s" % (a, b))
        return 1

# 測試用例
a_test = ATest()
a_test.test(1, "str")    # 參數正確
a_test.test("str", 1)    # 參數錯誤

(5)多個裝飾器同時裝飾一個函數,也比較簡單,直接看代碼:

# 使用裝飾器
@logging
@params_chack(int, str, (list, tuple))
def test05(a, b, c):
    print("in function test05, a=%s, b=%s, c=%s" % (a, b, c))
    return 1

# 測試用例
print(test05(1, "str", [1, 2]))        # 參數正確
print(test05(1, "str", (1, 2)))        # 參數正確
print(test05(1, "str", "str1str2"))    # 參數錯誤

(6)將裝飾器寫為類的形式,即“裝飾器類”。此時對于裝飾器類的要求是必須是可被調用的,即必須實現類的__call__方法。直接上代碼:

# 構建裝飾器類
class Decorator(object):
    def __init__(self, func):
        self.func = func
        return

    def __call__(self, *args, **kwargs):
        print("%s called" % self.func.__name__)
        result = self.func(*args, **kwargs)
        print("%s end" % self.func.__name__)
        return result

# 使用裝飾器
@Decorator
def test06(a, b, c):
    print("in function test06, a=%s, b=%s, c=%s" % (a, b, c))
    return 1

# 測試用例
print(test06(1, 2, 3))

這里的裝飾器類的構造函數中傳入func,使其能在__call__方法中被調用。同時這里的裝飾器類并沒有帶有參數,實現不了類似于參數檢查的功能。類似于上邊的思路,我們這里也可以構建帶有參數的裝飾器類,還是以參數檢查為例:

# 構建裝飾器類
class ParamCheck(object):

    def __init__(self, *types, **kwtypes):
        self.types = types
        self.kwtypes = kwtypes
        return

    def __call__(self, func):
        @functools.wraps(func)
        def _inner(*args, **kwargs):
            result = [isinstance(_param, _type) for _param, _type in zip(args, self.types)]
            assert all(result), "params_chack: invalid parameters"
            result = [isinstance(kwargs[_param], self.kwtypes[_param]) for _param in kwargs if _param in self.kwtypes]
            assert all(result), "params_chack: invalid parameters"
            return func(*args, **kwargs)
        return _inner

# 使用裝飾器
@ParamCheck(int, str, (list, tuple))
def test07(a, b, c):
    print("in function test06, a=%s, b=%s, c=%s" % (a, b, c))
    return 1

# 測試用例
print(test07(1, "str", [1, 2]))    # 參數正確
print(test07(1, "str", (1, 2)))    # 參數正確
print(test07(1, 2, (1, 2)))        # 參數錯誤

其他一些裝飾器實例

函數緩存:一個函數的執行結果可以被緩存在內存中,下次再次調用時,可以先查看緩存中是否存在,如果存在則直接返回緩存中的結果,否則返回函數調用結果。這種裝飾器比較適合裝飾過程比較復雜或耗時的函數,比如數據庫查詢等。

# 實例: 函數緩存
def funccache(func):
    cache = {}

    @functools.wraps(func)
    def _inner(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return _inner

# 使用裝飾器
@funccache
def test08(a, b, c):
    # 其他復雜或耗時計算
    return a + b + c

還有很多其他例子,比如函數調用計數、函數計時、函數自動重試等,思路都基本相同,這里就不一一列舉了。

Python中自帶的裝飾器

Python中自帶有三個和class相關的裝飾器:@staticmethod、@classmethod 和@property。

(1)先看@property,可以將其理解為“將類方法轉化為類屬性的裝飾器”。先看實例:

# 使用Python自帶的裝飾器
class People(object):

    def __init__(self):
        self._name = None
        self._age = None
        return

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name
        return

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        assert 0 < age < 120
        self._age = age
        return

p = People()
p.name = "tom"          # 設置name
p.age = 12              # 設置age
print(p.name, p.age)    # 輸出name和age
p.age = 120             # 設置age, 此時認為120為異常數據

這里定義一個People類,有兩個屬性name和age。當我們聲明了實例p,使用p操作name和age時,實際上是調用的name、age方法,此時會做參數檢查等工作。@property將name方法轉化為屬性,同時當對該屬性進行賦值時,會自動調用@name.setter將下邊的name方法。

@property有.setter、.getter和.deleter三中裝飾器,分別對應賦值、取值和刪除三種操作。

(2)@staticmethod 將類成員方法聲明為類靜態方法,類靜態方法沒有 self 參數,可以通過類名或類實例調用。

(3)@classmethod 將類成員方法聲明為類方法,類方法所接收的第一個參數不是self,而是cls,即當前類的具體類型。

靜態方法和類方法都比較簡單,一個簡單的例子解釋靜態方法和類方法:

# 類靜態方法和類方法
class A(object):
    var = 1

    def func(self):
        print(self.var)
        return

    @staticmethod
    def static_func():
        print(A.var)
        return

    @classmethod
    def class_func(cls):
        print(cls.var)
        cls().func()
        return

 

 

來自:https://zhuanlan.zhihu.com/p/23510985

 

 本文由用戶 Latrice2344 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!