python装饰器
装饰器
- 需求
- 一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
下面增加信息输出功能def add(x, y): return x + y
def add(x, y): print("call add, x + y") # 日志输出到控制台 return x + y
- 一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
- 上面的加法函数是完成了需求,但是有以下的缺点
- 打印是一个功能,这条语句和add函数耦合太高
- 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数add中
装饰器
- 下面代码做到了业务功能分离,但是fn函数调用传参是个问题
def add(x,y):
return x + y
def logger(fn): #测试用,拥有增强功能,但是不能更改参数,不能做到多次不同的参数测试
print('program begin') # 增强的输出
x = fn(4,5)
print('program end') # 增强的功能
return x
print(logger(add))
- 下面解决参数传输问题
def logger(fn,* args,** kwargs):
print('program begin')
x = fn(* args,** kwargs) # fn就是add函数对象,调用fn时,实际调用add(),此时参数可以在logger函数中传入,fn函数(add)拿到参数
print('program end')
return x
print(logger(add,5,y=60))
#将上代码柯里化
def logger(fn):
def wrapper(* args,** kwargs):
print('program begin')
x = fn(* args,** kwargs)
print('program working')
print('program end')
return x
return wrapper
#fe = logger(add)(5,y=60) # 装饰器柯里化写法
#print(fe)
add = logger(add) #先算等号右边,再赋值给左边,看似原来的函数add,实际上是重新定义过的函数add
print(add(x=5,y=60))
装饰器语法
def logger(fn):
def wrapper(* args,** kwargs):
print('program begin')
x = fn(* args,** kwargs)
print('program working')
print('program end')
return x
return wrapper
@logger # 等价于add = logger(add)
def add(x,y):
return x + y
print(add(45,40))
- 装饰器(无参)
- 它是一个函数
- 函数作为它的形参。无参装饰器实际上就是一个单形参函数
- 返回值也是一个函数
- 可以使用@functionname方式,简化调用
- 装饰器和高阶函数
- 装饰器可以是高阶函数,但装饰器只是对传入函数的功能的装饰(功能增强)
举例 :
import datetime
import time
def logger(foo):
def wrapper(*args,**kwargs):
print("{} program start".format(foo.__name__))
start = datetime.datetime.now()
print("{} program args={},kwargs={}".format(foo.__name__,args,kwargs))
cc = foo(* args,** kwargs)
print("{} program end".format(foo.__name__))
dalta = datetime.datetime.now()-start
print("{} program took {}".format(foo.__name__,dalta.total_seconds()))
return cc
return wrapper
@logger
def add(x,y):
"""This is a function of addition"""
"""The function of addition is """
time.sleep(0.1)
return x + y
add(10000,y=12)
print("name={},doc={}".format(add.__name__, add.__doc__))
_______________________________________________________________________
>>> add program start
>>> add program args=(10000,),kwargs={'y': 12}
>>> add program end
>>> add program took 0.101882
>>> name=target,doc=None
- name=target,ndoc=None 上面输出的属性,原函数的属性被改掉了,直接输出了装饰器函数target的属性,这是装饰器带来的副作用
Python的文档
- Python文档字符串Documentation Strings
- 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
- 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
- 可以使用特殊属性__doc__访问这个文档
def add(x,y):
"""This is a function of addition"""
"""The function of addition is """
return x + y
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
装饰器的副作用
- 原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性
下面进行改造装饰器
import datetime
import time
def copy_properties(src, dest): # 用此函数改造装饰器 src 为原函数,dest为需要改造的函数
dest.__name__ = src.__name__ # 用原函数name属性赋给装饰器内层函数的name属性
dest.__doc__ = src.__doc__ # 用原函数doc属性赋给装饰器内层函数的doc属性
def logger(foo):
def wrapper(*args, **kwargs):
print("{} program start".format(foo.__name__))
start = datetime.datetime.now()
print("{} program args={},kwargs={}".format(foo.__name__, args, kwargs))
cc = foo(* args, ** kwargs)
print("{} program end".format(foo.__name__))
dalta = datetime.datetime.now() - start
print("{} program took {}".format(foo.__name__, dalta.total_seconds()))
return cc
copy_properties(foo, wrapper) # 此语句是调用函数copy_properties函数,将foo(实际上是add的属性)的属性赋给wrapper
return wrapper # 返回wrapper函数时,已经接受到属性的更改
@logger
def add(x, y):
"""This is a function of addition
The function of addition is... """
time.sleep(0.1)
return x + y
add(10000, y=12)
print("name={},\ndoc={}".format(add.__name__, add.__doc__))
_______________________________________________________________________________________
>>>add program start
>>>add program args=(10000,),kwargs={'y': 12}
>>>add program end
>>>add program took 0.10073
>>>name=add,
>>>doc=This is a function of addition
The function of addition is...
name=add,
doc = This is a function of addition
The function of addition is
上面的输出属性被改过来了,可以直接输出原函数的属性
- 通过copy_properties函数将被包装函数的属性(add函数为被包装函数)覆盖掉包装函数target函数
- 凡是被装饰的函数都需要复制这些属性,这个函数很通用
- 可以将复制属性的函数构建成装饰器函数,带参装饰器
带参装饰器
import datetime
import time
def copy_properties(src):
def _copy(dest): # 此处装饰器柯里化
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
return dest
return _copy
def test(flag):
def logger(fn):
@copy_properties(fn) # 等效wrapper = copy_propertie(fn)(wrapper),此处用copy_propertie(fn)函数来装饰wrapper函数,装饰器里套装饰器,带参装饰器
def wrapper(* args, ** kwargs):
print("{} program start".format(fn.__name__))
start = datetime.datetime.now()
cc = fn(* args, ** kwargs)
dalta = datetime.datetime.now() - start
print("name= {},\ndoc={}".format(add.__name__, add.__doc__))
print("{} program end had took {}".format(fn.__name__, dalta.total_seconds()))
print(
"{} to slow".format(fn.__name__) if dalta.total_seconds() > flag else "{} OK,fast".format(fn.__name__))
return cc
return wrapper
return logger # 返回值是一个不带参的装饰器函数
@test(3) # 等效 add = test(3)(add),带参装饰器
def add(x, y):
"""This is a function of addition
The function of addition is..."""
time.sleep(2)
return x + y
add(10, 2) # 调用函数
_______________________________________________________________________________________
>>>add program start
>>>name= add,
>>>doc=This is a function of addition
The function of addition is...
>>>add program end had took 2.000189
>>>add OK,fast
- 带参装饰器
- 它是一个函数
- 函数作为它的形参
- 返回值是一个不带参的装饰器函数
- 使用@functionname(参数列表)方式调用
- 可以看做在装饰器外层又加了一层函数,这个函数可以多参数
带参装饰器输出目标记录
import datetime
import time
def copy_propertie(src):
def _copy(dest): # 此处装饰器柯里化
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
return dest
return _copy
def test(flag, func=lambda name, dalta: print('{} took {:.2f}s'.format(name, dalta))): # 此处设置,当函数达到要求记录时,则输出记录
def logger(fn):
@copy_propertie(fn) # 等效wrapper = copy_propertie(fn)(wrapper),此处用copy_propertie(fn)函数来装饰wrapper函数,装饰器里套装饰器,带参装饰器
def wrapper(* args, ** kwargs):
print("{} program start".format(fn.__name__))
start = datetime.datetime.now()
cc = fn(* args, ** kwargs)
dalta = (datetime.datetime.now() - start).total_seconds()
print("name= {},\ndoc={}".format(add.__name__, add.__doc__))
#print("{} program end had took {}".format(fn.__name__,dalta))
#print("{} to slow".format(fn.__name__) if dalta> flag else "{} OK,fast".format(fn.__name__) )
if dalta > flag: # 此处设置条件,当达到条件,则输出
func(fn.__name__, dalta)
return cc
return wrapper
return logger
@test(3) # add = test(3)(add)
def add(x, y):
"""This is a function of addition
The function of addition is..."""
time.sleep(3)
return x + y
add(10, 2)
____________________________________________________________________________
>>>add program start
>>>name= add,
>>>doc=This is a function of addition
The function of addition is...
>>>add took 3.00s
用模块,内建函数进行装饰
import datetime
import time
from functools import update_wrapper,wraps #调用模块,用内建函数进行装饰
# def copy_propertie(src):
# def _copy(dest): # 此处装饰器柯里化
# dest.__name__ = src.__name__
# dest.__doc__ = src.__doc__
# return dest
# return _copy
#
def test(flag, func=lambda name, dalta: print('{} took {:.2f}s'.format(name, dalta))): # 此处设置,当函数达到要求记录时,则输出记录
def logger(fn):
@function.wraps(fn) # 等效wrapper = copy_propertie(fn)(wrapper),此处用copy_propertie(fn)函数来装饰wrapper函数,装饰器里套装饰器,带参装饰器
def wrapper(* args, ** kwargs):
print("{} program start".format(fn.__name__))
start = datetime.datetime.now()
cc = fn(* args, **kwargs)
dalta = (datetime.datetime.now() - start).total_seconds()
print("name= {},\ndoc={}".format(add.__name__, add.__doc__))
#print("{} program end had took {}".format(fn.__name__,dalta))
#print("{} to slow".format(fn.__name__) if dalta> flag else "{} OK,fast".format(fn.__name__) )
if dalta > flag: # 此处设置条件,当达到条件,则输出
func(fn.__name__, dalta)
return cc
#update_wrapper(wrapper, fn) 与@wraper(fn)作用相同
return wrapper
return logger
@test(3) # add = test(3)(add)
def add(x, y):
"""This is a function of addition
The function of addition is..."""
time.sleep(3)
return x + y
add(10, 2)
functools 模块
-
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 等效 @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)
- 类似copy_properties功能
- wrapper 包装函数、被更新者,wrapped 被包装函数、数据源
- 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性’module’, ‘name’, ‘qualname’, ‘doc’, ‘annotations’
模块名、名称、限定名、文档、参数注解 - 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典
- 增加一个__wrapped__属性,保留着wrapped函数