装饰器
#####此篇文章来源于我的老师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')