chapter5.2装饰器

装饰器

有一个需求,将一个加法函数,增加一个功能,输出调用的参数,及调用信息

在源代码里插入代码,叫硬编码,不利于更改。非业务功能输出信息,不应该放在业务代码里。

def add(x,y):
    """ function add """
    return x+y

def logger(fn,*args,**kwargs):
    print('sdfasd')
    ret = fn(*args,**kwargs)
    return ret
print(logger(add,4,5))

 

定义两个函数,调用后加强输出,但是函数传参是个问题,使用以下方法,*args和**kwargs

将函数柯里化

def logger(fn):
    def wrapper(*args,**kwargs):
        print('Function is {}'.format(wrapper.__name__))
        ret = fn(*args,**kwargs)
        return ret
    return wrapper
def add(x,y):
  """ function add """
  return x+y
print(logger(add,4,5))

柯里化是为了带参装饰器的应用,后边有。

装饰器语法糖

def logger(fn):
    def wrapper(*args, **kwargs):
        print('Function is {}'.format(fn.__name__))
        ret = fn(*args, **kwargs)
        return ret
    return wrapper

@logger  ##==>add = logger(add)
def add(x, y):
    """ function add """
    return x + y

print(add(4, 4))###返回"Function is add"

文档字符串

Python的文档字符串Documentation Strings

在函数或者类的语句块下的第一行,一般使用三引号,因为文本大多是多行的

惯例是首字母大写,第一行概述,空一行,第三行写详细描述,

可以使用类或者类对象的__doc__访问该文档

 

装饰器(无参)

装饰器可以是函数,也可以是类,只要可调用就可以使用,返回值是一个函数或对象,使用@装饰器name,魔术方式,装饰器就是高阶函数,但装饰器是对传入函数的功能的增强(装饰)

add = logger(add)

这句中函数被重新覆盖,但是原来的add指向的地址被logger中的fn引用,仍然存在。这里fn使用了闭包

 装饰器的副作用:

def logger(fn):
    def wrapper(*args, **kwargs):
        '''This is a warp'''
        print('function is {}'.format(fn.__name__))##add
        print('doc: {}'.format(fn.__doc__))##add的文档
        ret = fn(*args, **kwargs)
        return ret
    return wrapper

@logger
def add(x, y):
    '''This is a function of addition'''
    return x + y

ret = add(4, 5)
print(ret, add.__doc__)##这里返回的文档是warp的,add本身的属性并没有显示

 

这里可以调用fn.__doc__查看文档属性,想要看原来add的文件属性,在全局只能看到logger的,add函数的文档只用原来的地址,现在被装饰器的wrapper的fn记录,因为函数内部的变量外部不可见,  

 

带参装饰器

python中提供有相应的函数,要是自己实现,可以使用两层装饰器。外层可以使用带参函数

 

def copy_properties(src):##源函数
    def copy_inner(dst):##目标函数
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return copy_inner

def logger(fn):
    @copy_properties(fn)  ####wrap = copy(fn)(wrap)    代参装饰器
    def wrapper(*args, **kwargs):
        '''This is a warp'''
        print('function is {}'.format(fn.__name__))
        print('doc: {}'.format(fn.__doc__))
        ret = fn(*args, **kwargs)
        return ret
    return wrapper

@logger  ###add = logger(add)
def add(x, y):
    '''This is a function of addition'''
    return x + y

ret = add(4, 5)
print(ret, add.__doc__)

注意装饰器的等价形式,带参装饰器的变换,将函数柯里化。

以上函数,copy函数修饰了logger,将函数add的属性复制到了函数warp上,只复制了名字和文档的属性,要想全部覆盖,可以使用wrapper函数

 

带参装饰器练习

要求获取函数的执行时常,对时常超过阈值的函数记录一下

import datetime,time,functools
def
copy_properties(source): def copy_inner(destin): destin.__name__ = source.__name__ destin.__doc__ = source.__doc__ return destin return copy_inner def logger(duration): def logger(fn): @copy_properties(fn) ##wrapper = copy_properties(fn)(wrapper) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() print('time out') if delta > duration else print('time enought quick') ##函数超时的模拟使用time.sleep方法,对超时的操作也可以提出 return ret return wrapper return logger @logger(3) ##add = logger(4)(add) def add(x,y): time.sleep(3) return x+y print(add(3,2))

将上个程序的logger函数改成以下的函数,就可以灵活的控制得到的结果的处理方式。


def
logger(duration,func=lambda name,duration:print('{} took {}'.format(name,duration))): def logger(fn): @copy_properties(fn) ##wrapper = copy_properties(fn)(wrapper) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() if delta > duration: func(fn.__name__,delta) return ret return wrapper return logger

 

带参装饰器是一个函数,函数作为形参,返回值是不带参数的装饰器函数,使用@函数名(参数列表)方式调用,可以看作装饰器外层又加了一层函数

 

functools类下的wraps方法

from functools import wraps 调用方法

import functools
import datetime
import time

def logger(duration,func=lambda name,duration:print('{} took {}'.format(name,duration))):
    def logger(fn):
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now()-start).total_seconds()
            if delta > duration:
                func(fn.__name__,delta)
            return ret
        return functools.update_wrapper(wrapper,fn)##更新文档
    return logger

@logger(3)  ##add = logger(4)(add)
def add(x,y):
    time.sleep(3)
    return x+y
print(add(3,2))

 

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=('__dict__',))

  类似于copy_properties功能

  wrapper包装函数,被更新者,

  wrapper被包装函数,数据源

  元组WRAPPER__ASSIGNMENTS中是要覆盖的属性

    ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')

    模块名,名称,限定名,文档,参数注解

  元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典,更新属性字典

  增加一个__wrapper__属性,保留wrapped的属性

 

import functools
import datetime
import time

def logger(duration,func=lambda name,duration:print('{} took {}'.format(name,duration))):
    def logger(fn):
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now()-start).total_seconds()
            if delta > duration:
                func(fn.__name__,delta)
            return ret
        return functools.update_wrapper(wrapper,fn)
    return logger

@logger(3)  ##add = logger(4)(add)
def add(x,y):
    time.sleep(3)
    return x+y
print(add(3,2))

 

装饰器使用,@wraps,装饰器方法

@functools.wraps( wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

wrapped 被包装函数

 

以下为类方法解决,没看到类请跳过。

装饰器还可以通过上下文管理实现。__enter__和__exit__

import time,datetime
from functools import update_wrapper

class TimeIt:
    '''This is wtapper function'''
    def __init__(self,fn):
        self.fn = fn
        update_wrapper(self,fn)##更新函数文档

    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn

    def __exit__(self, exc_type, exc_val, exc_tb):
        print((datetime.datetime.now()-self.start).total_seconds())

def add(x,y):
    '''This is add function'''
    time.sleep(1)
    return x+y

with TimeIt(add) as fn:##TimeIt(add)是TimeIt的实例对象,
    print(fn(4,5))

还可以通过创建实例对象,然后调用它实现,用到了__call__方法

import time,datetime
from functools import update_wrapper
class TimeIt:
    '''This is wtapper function'''
    def __init__(self,fn):
        self.fn = fn
        update_wrapper(self,fn)

    def __call__(self, *args, **kwargs):###实例可调用
        start = datetime.datetime.now()
        ret = self.fn(*args, *kwargs)
        print((datetime.datetime.now() - start).total_seconds())
        return ret

def add (x,y):
    '''This is add function'''
    time.sleep(1)
    return x+y

a = TimeIt(add)##实例赋值
print(a(4,5))##实例调用,这两句可以写为print(TimeIt(add)(4,5))

通过以上两例,可以感觉到,类十分象一个装饰器,其实类也可以作为装饰器。

import time,datetime
from functools import update_wrapper
class TimeIt:
    '''This is wtapper function'''
    def __init__(self,fn):
        self.fn = fn
        update_wrapper(self,fn)

    def __call__(self, *args, **kwargs):
        start = datetime.datetime.now()
        ret = self.fn(*args, *kwargs)
        print((datetime.datetime.now() - start).total_seconds())
        return ret

@TimeIt
def add (x,y):##创建实例对象时,调用__init__方法,完成后add指向类的对象,类对象的fn保存函数信息
    '''This is add function'''
    time.sleep(1)
    return x+y

print(add(3,2))##调用函数时,调用__call__方法,装饰的方法在call方法里

 

posted on 2018-08-29 22:56  Riper  阅读(134)  评论(0编辑  收藏  举报

导航