cookbook_元編程

1給函數添加一個包裝

問題:給函數加一個外包裝層,已添加額外的處理,例如,記錄日誌,計時統計等


解決方案:能夠定義一個裝飾器來包裝函數

2編寫裝飾器時如何保存函數的元數據

問題:當一個函數被裝飾器裝飾時,一些重要的元數據好比:函數名、文檔字符串、函數註解以及調用簽名都丟失了
解決方案:每當定義一個裝飾器時應該老是記得爲底層的包裝函數添加functools庫中的@wraps裝飾器

#問題:當一個函數被裝飾器裝飾時,一些重要的元數據好比:函數名、文檔字符串、函數註解以及調用簽名都丟失了

#解決方案:每當定義一個裝飾器時應該老是記得爲底層的包裝函數添加functools庫中的@wraps裝飾器

import time
import functools
def timethis(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        start_time = time.time()
        result = func(*args,**kwargs)
        end_time = time.time()
        print(func.__name__,end_time-start_time)
        return result
    return wrapper

@timethis
def mysleep(num:int):
    """
    原函數註釋文檔
    :param num:
    :return:
    """
    time.sleep(num)
    print("我是原函數")


mysleep(3)
print(mysleep.__name__)
print(mysleep.__doc__)
print(mysleep.__annotations__)


#若是裝飾器使用@functools.wraps(func) 裝飾,咱們就可使用下面的方法獲取到原函數!!!
mysleep.__wrapped__(3)
 

3對裝飾器進行解包裝

問題: 咱們已經把裝飾器添加到函數上了,可是想撤銷它,訪問未經包裝的原函數。

解決方案:假設裝飾器已經實現了@warps(func),通常來講咱們能夠經過訪問__wrapped__屬性來獲取到原函數

4定義一個可接收參數的裝飾器

問題:咱們想編寫一個可接收參數的裝飾器函數
解決方案:假設咱們想編寫一個爲函數添加日誌功能的裝飾器,可是又容許用戶指定日誌的等級以及一些其餘的細節操做做爲參數。
import logging
import functools


def logged(level,name=None,message=None):
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__


        @functools.wraps(func)
        def wrapper(*args,**kwargs):
            log.log(level,message)
            return func(*args,**kwargs)
        return wrapper
    return decorate

 

 5定義一個屬性可由用戶修改的裝飾器

問題:咱們想編寫一個裝飾器來包裝函數,可是可讓用戶調整裝飾器的屬性,這樣在運行時就可以控制裝飾器的行爲
from functools import wraps,partial
import logging

def attach_wrapper(obj,func=None):
    if func is None:
        return partial(attach_wrapper,obj)
    setattr(obj,func.__name__,func)
    return func

def logged(level,name=None,message=None):
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args,**kwargs):
            log.log(level,logmsg)
            return func(*args,**kwargs)

        @attach_wrapper(wrapper)
        def set_level(newlevel):
            nonlocal level
            level = newlevel

        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg


        return wrapper
    return decorate

logging.basicConfig(level=logging.DEBUG)
@logged(logging.DEBUG)
def add(x,y):
    return x + y

add(2,5)

add.set_message("Add called")
add(3,8)

 

6定義一個能接收可選參數的裝飾器

問題:咱們想編寫一個單獨的裝飾器,使其既能夠像@decorator 這樣不帶參數,也能夠像@decorator(x,y,z)這樣接收可選參數
from functools import wraps,partial
import logging


def logged(func=None,*,level=logging.DEBUG,name=None,message=None):
    if func is None:
        return partial(logged,level=level,name=name,message=message)

    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__
    @wraps(func)
    def wrapper(*args,**kwargs):
        log.log(level,logmsg)
        return func(*args,**kwargs)
    return wrapper



@logged
def add(x,y):
    logging.debug("hahahah")
    return x+y

#沒有參數時,裝飾器就至關於:logged(func),因此裝飾器的第一個參數就是func,其餘都是可選參數

@logged(level=logging.CRITICAL,name="example")
def spam():
    print("spam!!!")

#有參數時,裝飾器就至關於logged(level=logging.DEBUG,name="example")(spam)
#巧妙的利用functools.partial 將構建好的方法返回

add(1,2)
spam()

 

7利用裝飾器對函數參數強制執行類型檢查

問題:咱們想爲函數參數添增強制類型檢查功能,將其做爲一種斷言或者與調用者之間的契約
from inspect import signature
from functools import wraps

def typeassert(*ty_args,**ty_kwargs):

    def decorate(func):
        if not __debug__:
            return func
        sig = signature(func)#獲取func的參數簽名(x,y,z)
        bound_types = sig.bind_partial(*ty_args,**ty_kwargs).arguments# 參數簽名與類型參數作映射   [("x",<class "int">),("z",<class "int">)]
        @wraps(func)
        def wrapper(*args,**kwargs):
            bound_values = sig.bind(*args,**kwargs).arguments# 參數簽名與函數參數作映射
            for name,value in bound_values.items():
                if name in bound_types:#判斷參數是否有類型限制
                    if not isinstance(value,bound_types[name]):
                        raise TypeError("Argument {} must be {}".format(name,bound_types[name]))
            return func(*args,**kwargs)
        return wrapper
    return decorate



class A():
    def a(self):
        print("a")

@typeassert(int,A,z=int)
def add(x,y,z):
    print(x,y,z)
    return x

add(1,A(),3)


#想法:參數類型的限制可使用在參數處理方法中,對前端接收的參數進行檢查,也可使用在一些須要限制傳入參數類型的地方


#注:此裝飾器一個微妙的地方,只檢查傳遞的參數,若是是默認參數,沒有進行傳遞,參數類型不進行檢查


@typeassert(int,list)
def bar(x,items=None):
    if items is None:
        items = []
    items.append(x)
    return items

print(bar(2))

 

8在類中定義裝飾器

問題: 咱們想在類中定義一個裝飾器,並將其做用到其餘函數或方法上
from functools import wraps

class A:
    def decorator1(self,func):
        @wraps(func)
        def wrapper(*args,**kwargs):
            print("decorator 1")
            return func(*args,**kwargs)
        return wrapper

    @classmethod
    def decorator2(cls,func):
        @wraps(func)
        def wrapper(*args,**kwargs):
            print("decorator 2")
            return func(*args,**kwargs)
        return wrapper


#思考:@property 其實是一個擁有 getter(),setter(),deleter()方法的類,每個方法均可做爲一個裝飾器
#幾個裝飾器均可以操縱實例的狀態,所以,若是須要裝飾器在背後記錄或合併信息,這是一個很明智的方法。

 

9把裝飾器定義成類

問題: 咱們想用裝飾器來包裝函數,可是但願獲得的結果是一個可調用的實例。咱們須要裝飾器既能在類中工做,也能夠在類外部使用

解決方案:要把裝飾器定義成類實例,須要確保在類中實現__call__()和__get__()方法
import types
from functools import wraps

class Profield:
    def __init__(self,func):
        wraps(func)(self)
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls +=1
        return self.__wrapped__(*args,**kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self,instance)


#該裝飾器至關於爲函數添加一個屬性 ncalls

 

10把裝飾器做用到類和靜態方法上
問題:咱們想在類或者靜態方法上應用裝飾器

解決方案:將裝飾器做用到類和靜態方法上是簡單而直接的,可是要保證裝飾器在應用的時候須要放在@classmethod 和 @staticmethod 以前,示例以下:
import time
from functools import wraps

def timethis(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start = time.time()
        r = func(*args,**kwargs)
        end = time.time()
        print(end-start)
        return r
    return wrapper
@classmethod 和 @staticmethod 裝飾器並不會返回一個可執行對象,因此裝飾器都要放在他們下面!!!

11編寫裝飾器爲被包裝函數添加參數

問題:咱們想編寫一個裝飾器,爲被包裝的函數添加額外的參數,可是添加的參數不能影響到該函數已有的調用約定

解決方案:
from functools import wraps

def optinoal_debug(func):
    @wraps(func)
    def wrapper(*args,debug=False,**kwargs):
        if debug:
            print("Calling",func.__name__)
        return func(*args,**kwargs)
    return wrapper
函數中的一部分參數被裝飾器解析所用,剩下參數給到函數,能夠用被包裝函數的參數來控制裝飾器的行爲

12利用裝飾器給函數定義打補丁

#問題:咱們想檢查或改寫一部分類的定義,以此來修改類的行爲,可是不想經過繼承或者元類的方式來作


#解決方案:
def log_getattribute(cls):
    orig_getattribute = cls.__getattribute__


    def new_getattribute(self,name):
        print("getting",name)
        return orig_getattribute(self,name)

    cls.__getattribute__ = new_getattribute
    return cls

@log_getattribute
class A:
    def __init__(self,x):
        self.x = x

    def spam(self):
        pass

a = A(42)
a.x
能夠經過此方法對類的屬性作監控
相關文章
相關標籤/搜索