type
status
date
slug
summary
tags
category
icon
password

装饰器

能够在不改变原有函数或类的基础上,在原来的基础上添加额外的功能的代码,就叫做装饰器

由来

需求:为一个加法函数增加记录实参的功能

装饰器语法

@logger就是装饰器语法
  • 答疑一: fn和ret是什么?
    • fn为装饰器函数形参, 接收被装饰函数作为的实参, 故fn指向的即为别装饰函数
    • ret是被装饰函数的返回值. 如果被装饰函数有返回值, 则在功能增强后被返回, 如果被装饰函数没有返回值, 则ret指向的为None, 返回None.(一般情况, 函数没有返回值, 隐性返回None)
  • 答疑二: 被装饰函数为定长形参列表, 为什么在装饰器函数代码中fn(*args, **kwargs)写成这样?
    • 通用写法, 当被装饰函数形参列表为定长时, 也可以这么写, 同时如果被装饰函数为不定长参数列表的函数, 同样适用. 兼容性更强.

无参装饰器

  • 上面例子的装饰器语法,称之为无惨装饰器
  • @符号后是一个函数
  • 虽然是无参装饰器,但是@后的函数本质上是单参函数
  • 上例的logger函数是一个高阶函数

日志记录装饰器实现

装饰器本质

  • 习惯上add函数被称为被包装函数wrapped,增强它的函数称为包装器、包装函数wrapper
  • 被包装函数就如有一幅画,包装它的目的是为了增强,而不是破坏画,采用非侵入式代码
  • 可以在原画前或后加入增强代码
  • 装饰器如同画框,装饰器可以更换,如同更换画框,画不变。也可以画框不变,更换画。也可以画框外再加其他装饰器。
 

带参装饰器


文档字符串

  • Python文档字符串Documentation Strings
  • 在函数(类、模块)语句块的第一行,且习惯是多行文本,所以多使用三引号
  • 文档字符串也算是合法的一条语句
  • 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
  • 可以使用特殊属性__doc__访问这个文档

装饰器的文档字符串问题

在上一节无参装饰器的装饰器语法例子中,print(add.__name__, add.__doc__) 语句执行后,发现add函数的函数名和文档都变了。如何解决?
函数也是对象,特殊属性也是属性,也可以被覆盖。现在访问add函数实际上是wrapper函数,所以使用原定义的add函数的名称和文档属性覆盖wrapper的对应属性就可以了。
如果copy_properties是公用的函数,可以定义成全局的。实际上,这个函数很通用,基本上装饰器都会用这个问题

带参装饰器

能否把copy_properties改成装饰器?这个装饰器是带参装饰器。
@ copy_properties(fn) 这种在装饰器后面跟着参数的装饰器称为带参装饰器
 
对logger设定一个阈值,对执行时长超出阈值的记录一下。
(对logger函数进一步柯里化,添加参数,logger(a, b ,c ,fn)→ logger(a, b ,c)(fn))
logger函数中,添加多个形参的处理方式
进一步提取记录功能,因为有可能输出到控制台,也可能写入日志,这个由一个函数提供。

属性更新

上面例子中copy_properties是通用的功能,标准库中functools已经提供了
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WARPPER_UPDATES)
  • 类似copy_properties功能
  • wrapper包装函数、被更新者,wrapped被包装函数、数据源
  • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性,由模块名__module__ ,名称__name__,限定名__qualname__,文档__doc__,参数注解__annotations__
  • 元组WRAPPER_UPDATES中是要被更新的属性__dic__属性字典
  • 增加一个__wrapped__属性,保留着wrapped函数
 
。。。。。

总结

  • @之后不是一个单独的标识符,是一个函数调用
  • 函数调用的返回值又是一个函数,此函数是一个无参装饰器
  • 带参装饰器,可以有任意个参数
    • @func()
    • @func(1)
    • @func(1, 2)