Python0502-装饰器

装饰器需求

  • 一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
def add(x,y):
    return x+y
#  增加信息输出功能
def add(x, y):
    print("calladd, x+y")  # 输出到控制台
    return x+y
  • 上面的加法函数是完成了需求但是有以下的缺点
    • 打印语句的耦合太高
    • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中

改进需求,业务功能分离

  • 做到了业务功能分离,但是fn函数调用传参是个问题
def add(x, y):
    return x + y

def logger(fn):
    print('begin')  # 增强的输出
    x = fn(4, 5)
    print('end')  # 增强的功能
    return x

print(logger(add))
----------------------------
begin
end
9

改进需求,传参

  • 解决了传参的问题,进一步改变
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, 4, y=5))
--------------------------------
begin
end
9

改进需求,柯里化

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

def logger(fn):
    def _logger(*args, **kwargs):
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    return _logger

print(logger(add)(5, y=50))
# 可以换一种写法
add = logger(add)  # 内层函数_logger,先计算=右边,add1重新赋值并定义,fn已经闭包被执行
print(add(x=5, y=10))  # logger(add, x=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, 40))
  • @logger是什么?这就是装饰器语法

装饰器说明

  • 装饰器(无参)
    • 它是一个函数
    • 函数作为它的形参
    • 返回值也是一个函数
    • 可以使用@functionname方式,简化调用
  • 装饰器和高阶函数
    • 装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)

代码演示

import datetime
import time

def logger(fn):
    def wrap(*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 {}s."
              .format(fn.__name__, duration.total_seconds()))
        return ret
    return wrap


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


print(add(4, y=7))
---------------------------------
args=(4,), kwargs={'y': 7}
===call add===========
function add took 2.016632s.       # 注意:这里显示的函数是add
11

怎样理解装饰器呢?

画框装饰器函数
玻璃前置功能增强
被增强函数这个是核心
背板后置功能增强

文档字符串

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


print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print("====================")
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 for add"""
    return x + y


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

包装函数属性

  • 提供一个函数,被封装函数属性 ==copy==> 包装函数属性
def copy_properties(src, dst):
    # 被封装函数属性 ==copy==> 包装函数属性(理解成把外层的函数属性传给内层函数属性)
    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)  # fn对应的是src,wrapper对应的是dst
    return wrapper

@logger
def add(x, y):
    """This is a function for add"""
    return x + y


print("name={}\ndoc={}".format(add.__name__, add.__doc__))
-------------------------------------
name=add
doc=This is a function for add

包装函数属性说明

  • 通过copy_properties函数将被包装函数的属性覆盖掉包装函数
  • 凡是被装饰的函数都需要复制这些属性,这个函数很通用
  • 可以将复制属性的函数构建成装饰器函数,带参装饰器

包装函数柯里化

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


def logger(fn):
    @copy_properties(fn)  # wrapper = copy_properties(fn, wrapper)
    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 for add"""
    return x + y


print("name={}\ndoc={}".format(add.__name__, add.__doc__))
---------------------------------------------
name=add
doc=This is a function for add

带参装饰器

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

def copy_properties(src):  # 柯里化
    def _copy(dst):
        # 被封装函数属性 ==copy==> 包装函数属性
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy


def logger(duration):
    def _logger(fn):
        @copy_properties(fn)  # wrapper = copy_properties(fn, wrapper)
        def wrapper(*args, **kwargs):
            """I am wrapper"""
            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


@logger(5)  # add = logger(5)(add)
def add(x, y):
    """This is a function for add"""
    time.sleep(3)
    return x + y


print(add(5, 6))
-----------------------------------------
so fast
11

带参装饰器总结

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

带参装饰器灵活控制

  • 将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
import datetime
import time

def copy_properties(src):  # 柯里化
    def _copy(dst):
        # 被封装函数属性 ==copy==> 包装函数属性
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    return _copy


def logger(duration,
           func=lambda name,
            duration:print("{} took {}s".format(name, duration))):
    def _logger(fn):
        @copy_properties(fn)  # wrapper = copy_properties(fn, wrapper)
        def wrapper(*args, **kwargs):
            """I am wrapper"""
            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


@logger(2)  # add = logger(2)(add)
def add(x, y):
    """This is a function for add"""
    time.sleep(3)
    return x + y


print(add(5, 6))
--------------------------------------
add took 2s
11

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属性字典
    • 增加一个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):
            """I am wrapper"""
            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 functools.update_wrapper(wrapper, fn)
        # 这里为啥是(wrapper, fn),functools.update_wrapper(wrapper, wrapped)wrapper 包装函数、被更新者,wrapped 被包装函数、数据源
    return _logger


@logger(2)  # add = logger(2)(add) # logger(2) => _logger add=_logger(add)
def add(x, y):
    """This is a function for add"""
    time.sleep(1)
    return x + y


print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')
-------------------------------------
11
add
<function add at 0x0000024974109158>
{'__wrapped__': <function add at 0x0000024974109158>}

functools模块@

  • @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)  # wrapper = functools.wraps(fn, wrapper)
        # 为啥是fn?@functools.wraps(wrapped), wrapped是被包装的函数
        def wrapper(*args, **kwargs):
            """I am wrapper"""
            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


@logger(2)  # add = logger(2)(add)
def add(x, y):
    """This is a function for add"""
    time.sleep(1)
    return x + y


print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')
---------------------------------
11
add
<function add at 0x000001F674779158>
{'__wrapped__': <function add at 0x000001F674779158>}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值