Python装饰器

装饰器


#####此篇文章来源于我的老师Wayne,仅作记录以备复习


  • 需求
    • 一个加法函数,想要增强它的功能,能够输出被调用过的以及调用的参数信息
def add(x,y):
    return x + y

增加信息输出功能

def add(x,y):
    print('call add,x + y')
    return x + y
  • 上面的加法函数是完成了需求,但是有以下缺点
    • 打印是一个功能,这条语句和 add 函数耦合太高
    • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数 add 中
  • 下面代码做到了业务分离功能,但是 fn 函数调用传参是个问题
def logger(fn):
    print('begin')
    x = fn(4,5)
    print(x)
    print('end')
    return x

print(logger(add))
  • 解决了传参的问题,进一步改变
def add(x,y):
    return x + y
def logger(fn,*args,**kwargs):
    print('begin')
    x = fn(*args,**kwargs)
    print('end')
    return x

print(logger(add,5,y=10))
  • 柯里化
  • 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。
    (来自维基百科定义)
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))

print(logger(add,5,y=10)) ==》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    等价于 add = logger(add)
def add(x,y):
    return x + y 

print(add(45,20))

FAQ:

@logger 是什么?

  • 这就是装饰器语法

  • 装饰器(无参)

    • 它是一个函数
    • 函数作为它的形参。无参装饰器实际上就是一个单形参函数
    • 返回值也是一个函数
    • 可以使用 @functionname方式,简化调用
      ***注:此处定义只是我目前所学总结,并不准确,只是方便理解
  • 装饰器和高阶函数

    • 装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)

import datetime
import time

def logger(fn):
    def wrapper(*args,**kwargs):
		# before 功能增强
        print('args = {},kwargs = {}'.format(args,kwargs))
        start = datetime.datetime.now()
        ret = fn(*args,**kwargs)
        # after 功能增强
        duration = datetime.datetime.now() - start
        print('function {} took {:.2f} s'.format(fn.__name__,duration.total_seconds()))
        return ret
    return wrapper

@logger    # 相当于 add = logger(add)
def add(x,y):
    print('====call add=====')
    time.sleep(2)
    return x + y

print(add(4,y=7))

文档字符串

  • Python 的文档
    • Python 文档字符串 Documentation Strings
    • 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
    • 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
    • 可以使用特殊属性__doc__访问这个文档
def add(x,y):
    """This is a function of addition"""
    return x + y

print('name = {}\ndoc = {}'.format(add.__name__,add.__doc__))
print(help(add))
name = add
doc = This is a function of addition
Help on function add in module __main__:

add(x, y)
    This is a function of addition

None

装饰器

  • 副作用
def logger(fn):
    def wrapper(*args,**kwargs):
        'i am 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 of addition'''
    return x + y

print('name = {}\ndoc = {}'.format(add.__name__,add.__doc__))
name = wrapper
doc = i am wrapper
  • 原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决?

  • 提供一个函数,被封装属性 == copy ==>包装函数属性
def copy_properties(src,dst):
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
    
def logger(fn):
    def wrapper(*args,**kwargs):
        'i am wrapper'
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    copy_properties(fn,wrapper)
    return wrapper


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

print('name = {}\ndoc = {}'.format(add.__name__,add.__doc__))
name = add
doc = This is a function of addition
  • 通过 copy_properties 函数将被包装函数的属性覆盖掉包装函数
  • 凡是被修饰的函数都需要复制这些属性,这个函数很通用
  • 可以将复制属性的函数构建成装饰器函数,带参装饰器

  • 提供一个函数,被封装函数属性 == copy ==>包装函数属性,改造成带参装饰器
def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy

def logger(fn):
    @copy_properties(fn)
    def wrapper(*args,**kwargs):
        'i am wrapper'
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    return wrapper

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

print('name = {}\ndoc = {}'.format(add.__name__,add.__doc__))
name = add
doc = This is a function of addition

带参装饰器

  • 需求:获取函数的执行时长,对时长超过阈值的函数记录一下
import datetime
import time

def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy

def logger(duration=3):
    def _logger(fn):
        @copy_properties(fn)
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta = (datetime.datetime.now() - start).total_seconds()
            print('so slow')if delta > duration else print('so fast')
            return ret
        return wrapper
    return _logger

@logger(5)
def add(x,y):
    time.sleep(4)
    return x + y

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

  • 将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
def logger(duration,func=lambda name,delta: print('{} took {:2f}s'.format(name,delta))):
    def _logger(fn):
        @copy_properties(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 wrapper
    return _logger
                
@logger(2)
def add(x,y):
    time.sleep(4)
    return x + y

print(add(5,6))

functools 模块

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

  • 类似 copy_properties功能
  • wrapper 包装函数、被更新者,wrapped 被包装函数、数据源
  • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
  • ‘__ module__’,’__ name__’,’__ qualname__’,’__ doc__’,’__ annotations__’
    模块名、名称、限定名、文档、参数注解
  • 元组 WRAPPER_UPDATES 中是要被更新的属性,__ dict__属性字典
  • 增加一个 __ wrapped__属性,保留着wrapped函数
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)
def add(x,y):
    time.sleep(3)
    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__属性字典
    • 增加一个 __ wrapped__属性,保留着wrapped函数
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)
def add(x,y):
    time.sleep(3)
    return x + y

print(add(5,6),add.__name__,add.__wrapped__,add.__dict__,sep='\n')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值