python 装饰器

需求

  • 一个加法函数,想增强它的功能,能够输出被调用过以及调用过参数的信息
def add1(x,y):
    print("call add, x+y") #日志输出的控制台
    return x+y

以上函数完成了需求,但是存在以下缺点:

  • 打印语法的耦合太高
  • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中

业务功能分离

def add1(x,y):
    return x+y
def logger(fn):
    print("begin")
    x = fn(4,5)
    print("end")
    return x
print(logger(add1))

但这里fn调用传参是个问题

解决传参的问题

def add1(x,y):
    return x+y
def logger(fn,*args,**kwargs):
    print("begin")
    x = fn(*args,**kwargs) #这里是在解构
    print("end")
    return x
print(logger(add1,5,y=10))

柯里化

def add(x,y):
    return x+y
def logger(fn):
    def wrapper(*args,**kwargs):
        print("begin")
        x = fn(*args,**kwargs) #这里是在解构
        print("end")
        return x
    return wrapper


print(logger(add)(5,y = 10))

装饰器语法糖

def logger(fn):
    def wrapper(*args,**kwargs):
        print("begin")
        x = fn(*args,**kwargs) #这里是在解构
        print("end")
        return x
    return wrapper
@logger
def add(x,y):
    return x+y

print(add(45,500))

@logger是装饰器的语法

装饰器(无惨)

  • 是一个函数
  • 函数作为它的参数
  • 返回值也是一个函数
  • 可以使用@functionname方式,简化调用

装饰器和高阶函数

  • 装饰器是高阶函数,但装饰器是对传入函数的功能是装饰(增强)
import datetime
import time
def logger(fn):
    def wrapper(*args,**kwargs):
        print("begin")
        print("args={},kwargs={}".format(args,kwargs))
        start = datetime.datetime.now()
        x = fn(*args,**kwargs) #这里是在解构
        duration = datetime.datetime.now() -start
        print("function {} took {}s.".format(fn.__name__,duration.total_seconds()))
        return x
    return wrapper
@logger # add = logger(add)
def add(x,y):
    print("==========call add==========")
    time.sleep(2)
    return x+y

print(add(45,500))

Python 文档字符串

python文档

  • python文档字符串Documentation Strings
  • 在函数语句块的第一行,且习惯是多行的文本,所以多引用三引号
  • 惯例是首字符大写,第一行写概述,空一行,第三行写详细描述
  • 可以使用特殊属性__doc__访问该文档
def add(x,y):
    """This is function of addition"""
    a = x+y
    return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(help(add))

副作用出现的问题:

def logger(fn):
    def wrapper(*args,**kwargs):
        "I am a wrapper"
        print("begin")
        x = fn(*args,**kwargs) #这里是在解构
        print("end")
        return x
    return wrapper
@logger #add= logger(add)
def add(x,y):
    "This is a function for add"
    return x+y
print("name={},doc={}".format(add.__name__,add.__doc__))

 原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决呢?

提供一个函数,被封装函数属性==copy==>包装函数属性

def copy_properties(src,dest): #可以改造成装饰器
    dest.__name__ = src.__name__
    dest.__doc__ = src.__doc__

def logger(fn):
    def wrapper(*args,**kwargs):
        "I am a wrapper"
        print("begin")
        x = fn(*args,**kwargs) #这里是在解构
        print("end")
        return x
    copy_properties(fn,wrapper)
    return wrapper
@logger #add= logger(add)
def add(x,y):
    "This is a function for add"
    return x+y
print("name={},doc={}".format(add.__name__,add.__doc__))
  • 通过copy_properties函数将被包装函数的属性覆盖掉包装函数
  • 凡是被装饰的函数都需要复制这些属性,这个函数很通用
  • 可以将复制属性的函数构建成装饰器函数,带参装饰器

改造成带参装饰器

def copy_properties(src):#可以改造成装饰器
    def _copy_properties(dest):
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _copy_properties
def logger(fn):
    @copy_properties(fn) #wrapper = copy_properties(fn)(wrapper)
    def wrapper(*args,**kwargs):
        "I am a wrapper"
        print("begin")
        x = fn(*args,**kwargs) #这里是在解构
        print("end")
        return x
    return wrapper
@logger #add= logger(add)
def add(x,y):
    "This is a function for add"
    return x+y
print("name={},doc={}".format(add.__name__,add.__doc__))

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

import datetime
import time
def copy_properties(src):#可以改造成装饰器
    def _copy_properties(dest):
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _copy_properties
def loger(duration):
    def _logger(fn):
        @copy_properties(fn)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            x = fn(*args, **kwargs)
            delta=(datetime.datetime.now()-start).total_seconds()
            print('so slow') if delta > duration else print('so fast')
            return x
        return wrapper
    return _logger
@loger(5)
def add(x,y):
    time.sleep(6)
    return x + y
print(add(10,24))

带参装饰器:

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

打印功能提出来

import datetime
import time
def copy_properties(src):#可以改造成装饰器 add
    def _copy_properties(dest): #wrapper
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _copy_properties
def loger(duration,func=lambda name,duration:print('{} took {}s'.format(name,duration))):  #loger(5)(add)  duration=5
    def _logger(fn):  #loger(5)(add) fn = add
        @copy_properties(fn) #wrapper = _copy_properties(fn)(wrapper)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            x = fn(*args, **kwargs)
            delta=(datetime.datetime.now()-start).total_seconds()
            if delta > duration:
                func(fn.__name__,duration)
            return x
        return wrapper
    return _logger
@loger(5) #add = loger(5)(add)
def add(x,y):
    time.sleep(6)
    return x + y
print(add(10,24))

functools模块

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

  • 类似copy_properties功能
  • wrapper 包装函数,被更新者,wrapped 被包装函数,数据源
  • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性,'__module__', '__name__', '__qualname__', '__doc__', '__annotations__'  模块名、名称、限定名、文档、参数注解
  • 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
  • 增加一个__warpped__属性,保留着warpped函数
import datetime, time, functools
def logger(duration, func=lambda name, duration: print('{} took {}s'.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__, duration)
            return ret
        return functools.update_wrapper(wrapper, fn)
    return _logger
@logger(5) # add = logger(5)(add)
def add(x,y):
    time.sleep(1)
    return x + y

print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')

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

  • 类似copy_properties功能
  • wrapped 被包装函数
  • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性,'__module__', '__name__', '__qualname__', '__doc__', '__annotations__'  模块名、名称、限定名、文档、参数注解
  • 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
  • 增加一个__warpped__属性,保留着warpped函数
import datetime, time, functools
def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
    def _logger(fn):
        @functools.wraps(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__, duration)
            return ret
        return wrapper
    return _logger
@logger(5) # add = logger(5)(add)
def add(x,y):
    time.sleep(1)
    return x + y

print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')

 

posted @ 2019-08-19 17:25  大胖猴  阅读(68)  评论(0编辑  收藏