Day21:裝飾器

書和咖啡發表於2019-02-21

今天要學的是裝飾器,裝飾器在python中會經常使用到,且裝飾器在不同場景下使用也不同,那到底什麼是裝飾器呢?

1.什麼是裝飾器

  • 裝飾器其實就是一個閉包,把一個函式當做引數然後返回一個替代版函式;

  • 裝飾器的功能在於對函式或類功能的增強,這是一種低耦合的功能增強;

2.裝飾器特點

  • 開放封閉原則,即對擴充套件是開放的,對修改時封閉的;

  • 裝飾器本質可以是任意可呼叫的物件,被裝飾的物件也可以是任意可呼叫物件;

  • 裝飾器的功能是在不修改被裝飾器物件原始碼以及被裝飾器物件的呼叫方式的前提下為其擴充套件新功能;

  • 裝飾器本質是函式,(即裝飾其他函式)就是為其他函式新增附加功能;

3.無參裝飾器

# 例1:這就是裝飾器的原型,目的是對add函式增強,在add執行的前後可以做一些操作。
def add(x, y):
    return x + y
def decorator(fn):
    def wrapper(*args, **kwargs):
        print('start')
        result = fn(*args, **kwargs)
        print('end')
        return result
    return wrapper
wrapper = decorator(add)
result = wrapper(x=1, y=2)
print(result)

# 例2:可以將例1的呼叫過程進行改造
def add(x, y):
    return x + y
def decorator(fn):
    def wrapper(*args, **kwargs):
        print('start')
        result = fn(*args, **kwargs)
        print('end')
        return result
    return wrapper
result = decorator(add)(x=1, y=2)
print(result)


# 例3:decorator 就是裝飾器函式,add就是被裝飾的函式
def decorator(fn):
    def wrapper(*args, **kwargs):
        print('start')
        result = fn(*args, **kwargs)
        print('end')
        return result
    return wrapper

#這是Python的裝飾器語法糖,@decorator相當於對add函式進行了一次呼叫,返回的是wrapper
@decorator
def add(x, y):
    return x + y
result = add(x=1, y=2)
print(result)


# 例4:解決被裝飾器裝飾的函式屬性不一致的問題
import functools
def decorator(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print('start')
        result = fn(*args, **kwargs)
        print('end')
        return result
    return wrapper

@decorator
def add(x, y):
    return x + y
result = add(x=1, y=2)
print(result)
print(add.__name__)  # add

  • 就是不帶引數的裝飾器,是最簡單的裝飾器,返回包裹函式;

  • *args表示的引數以列表的形式傳入;

  • **kwargs表示的引數以字典的形式傳入;

  • @decorator這個語法相當於 執行 func = decorator(func),為func函式裝飾並返回,且@修飾符必須出現在函式定義前一行,不允許和函式定義在同 一行;

4.帶參裝飾器

import functools
import datetime
import time

def decorator(t): # 這裡只是一個簡單的函式
    def timer(fn): # 這裡才是裝飾器
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            '''
            this is a wrap function
            '''
            print("args={} kwargs={}".format(args,kwargs))
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)
            end = datetime.datetime.now()
            duration = (end - start).total_seconds()
            if duration > t:
                print('Time out')
            return ret
        return wrapper
    return timer

@decorator(2)
def add(x,y):
    '''
    :param x:
    :param y:
    :return: 返回x + y的結果
    '''
    time.sleep(3)  # 延遲3秒
    return x + y
add(1,2)
print(add.__name__, add.__doc__, sep='\n')
  • 就是帶引數的裝飾器,即複雜裝飾器,返回包裹函式;

  • 實現一個裝飾器,它用來檢查被裝飾函式的引數型別,裝飾器可以通過引數指明函式引數的型別,呼叫時如果檢測出型別不匹配則丟擲異常。

5.functools模組

@functools.wraps(fn)
  • 用來拷貝屬性;

  • functools,用於高階函式,指那些作用於函式或者返回其它函式的函式,通常只要是可以被當做函式呼叫的物件就是這個模組的目標。

6.裝飾器在不同場景下的使用

(1)帶參裝飾器:不返回包裹函式

# 這裡裝飾器返回的並不是包裹的wrapper函式,因為這裡沒有wrapper函式,而是被裝飾的函式本身
# 這裡裝飾器的意義在於:呼叫被裝飾函式之前可以做一些事情
import datetime
import time
def decorator(t):
    def timer(fn):
        start = datetime.datetime.now()
        duration = (datetime.datetime.now() - start).total_seconds()
        print(duration)  # 0.0
        print(t)  # 2
        return fn
    return timer
@decorator(2)
def add(x,y):
    time.sleep(2)
    return x+y
print(add(1,2))
print(add.__name__)
  • 裝飾器返回的並不是包裹函式,而是直接返回原始函式,這種情況只能在呼叫原始函式之前做一些功能增強;

  • 如果返回包裹函式,不僅在呼叫函式之前做功能增強,呼叫函式之後也能執行功能增強;

(2)不帶參裝飾器:不返回包裹函式,返回原始函式本身

# 不帶引數的裝飾器,直接返回被裝飾函式本身,在原始函式之前做一些事情
import time
def decorator(fn):
    print('呼叫函式之前我可以做些事情')
    return fn
@decorator
def add(x,y):
    time.sleep(2)
    print(x+y)  # 3
add(1,2)
  • 裝飾器同樣是直接返回被裝飾函式本身,在原始函式之前做一些事情;

  • 函式裝飾器裝飾類:返回原始類本身(不帶引數)

  • 在呼叫一個類之前給這個類封裝一些屬性和方法,然後呼叫這個類時就可以直接使用這些方法和屬性;

(3)函式裝飾器裝飾類:返回原始類本身(帶引數)

def dec(cls):
    cls.num = 100
    return cls
@dec
class Person(): # Person = cls
    def __init__(self):
        pass
print(Person.num) # 100
  • 在帶參裝飾器第一次呼叫的時候只是一個簡單函式呼叫,返回的那個函式才是正真的裝飾器,裝飾器裝飾了作為引數傳遞給它的那個類,然後這個引數繫結了一個屬性,呼叫這個引數的類變數,就是第一次簡單函式呼叫所帶的引數;

(4)函式裝飾器裝飾類:返回原始類本身(帶引數)

def dec(num):
    def wrapper(cls): # 這裡的cls就是Person類
        cls.num = num
        return cls
    return wrapper
# 這種帶引數的裝飾器就理解為函式呼叫即可
@dec(100)
class Person():
    def __init__(self):
        pass
print(Person.num) # 100

7.類裝飾器

import datetime
import time
from functools import wraps
# 這是上下文管理的類
class Decorator:
    '''this is dec class '''
    def __init__(self, fn):
        if fn is not None:
            self.fn = fn
            wraps(fn)(self)  # 這裡是將fn的屬性賦值給self例項,因為self例項會返回
    def __call__(self, *args, **kwargs):
        self.start = datetime.datetime.now()
        ret = self.fn(*args, **kwargs)
        self.interval = (datetime.datetime.now() - self.start).total_seconds()
        return 'interval is {}, result is {}'.format(self.interval, ret)
# 被裝飾的函式
@Decorator    # add = Decorator(add)
def add(x,y):
    ''' this is a function '''
    time.sleep(2) # 延遲2秒
    return x + y
print(add(3,4))  # add呼叫的是Decorator的例項self,不是以前的函式add了
print(add.__doc__)
  • 類裝飾器顧名思義用類寫的裝飾器,類可以裝飾類,也可以裝飾函式;

  • 使用類裝飾器可以通過繼承的方式擴充套件類裝飾器的行為;

參考:俠課島(9xkd.com)Python同學計劃

相關文章