装饰器的副作用
def fn():
'''this is fn'''
help(fn)
#结果:
Help on function fn in module __main__:
fn()
this is fn
这就是一个函数的文档。
这里help函数好像其实就是调用了函数下面的注释。
其实和默认值一样,文档是存在__doc__
里面的,如下:
print(fn.__doc__)
# 结果
this is fn
我们可以通过dir(fn)
来获取它的所有属性。
import datetime
def logger(fn):
def warp(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
print('{} called took {}'.format(fn.__name__, end - start))
return ret
return warp
import time
@logger
def sleep(x):
time.sleep(x)
>>> sleep.__name__
'warp'
如上的装饰器,这里可以发现调用__name__
属性后出来的是wrap而不是sleep,这就是用了装饰器后的副作用。
当然,我们可以通过赋值的方式来解决:
import datetime
def logger(fn):
def warp(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
print('{} called took {}'.format(fn.__name__, end - start))
return ret
warp.__name__ = fn.__name__
warp.__doc__ = fn.__doc__
return warp
import time
@logger
def sleep(x):
time.sleep(x)
print(sleep.__name__)
# 结果
sleep
或者采用调用函数的方式:
def copy_properties(src, dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
def logger(fn):
def warp(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
print('{} called took {}'.format(fn.__name__, end - start))
return ret
copy_properties(fn, warp)
# warp.__name__ = fn.__name__
# warp.__doc__ = fn.__doc__
return warp
柯里化
柯里化其实是一个数学概念,目的是让多个参数变成一个参数。
def copy_properties(src):
def _copy(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return _copy
def logger(fn):
def warp(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
print('{} called took {}'.format(fn.__name__, end - start))
return ret
copy_properties(fn)(warp)
# copy_properties(fn, warp)
# warp.__name__ = fn.__name__
# warp.__doc__ = fn.__doc__
return warp
这个就叫柯里化。
这里copy_properties
返回的是一个装饰器。于是可以改造成如下:
def logger(fn):
@copy_properties(fn)
def warp(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
print('{} called took {}'.format(fn.__name__, end - start))
return ret
return warp
这里柯里化的目的就是为了做这个带参数的装饰器。
functools
import functools
help(functools.wraps)
>>> help(functools.wraps)
Help on function wraps in module functools:
wraps(wrapped, assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'), updated=('__dict__',))
Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
用法:
import functools
#help(functools.wraps)
def logger(fn):
@functools.wraps(fn)
def wrap(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
print('{} called took {}'.format(fn.__name__, end - start))
return ret
return wrap
@logger
def sleep(x):
time.sleep(x)
sleep(3)
# 结果
sleep called took 0:00:03.015098
这里@functools.wraps(fn)
就替代了@copy_properties(fn)
的作用。
带参数的装饰器
另外,functools.wrap还带有参数:
假如现在只想记录大于1秒的时间,并且对这个1秒单位作控制,通过参数传递进来。
时间差计算:
>>> s = datetime.datetime.now()
>>> e = datetime.datetime.now()
>>> type(e-s)
<class 'datetime.timedelta'>
如下,计算这个属性:
>>> delta = e - s
>>> delta.total_seconds()
2.3e-05
所以整个logger装饰器:
def logger(s):
def _logger(fn):
@functools.wraps(fn)
def wrap(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
if (end-start).total_seconds() > s:
print('call {} took {}'.format(fn.__name__, end - start))
return ret
return wrap
return _logger
这样我们就定义了一个带参数的装饰器:
@logger(2)
def sleep(x):
time.sleep(x)
sleep(2)
# 结果
call sleep took 0:00:02.016245
这种就叫做带参数的装饰器。一个函数,返回一个不带参数的装饰器。
多参数
def logger(s, p=lambda name, t:print('call {} took {}'.format(name, t))):
def _logger(fn):
@functools.wraps(fn)
def wrap(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
end = datetime.datetime.now()
if (end-start).total_seconds() > s:
p(fn.__name__, end - start)
return ret
return wrap
return _logger
@logger(2)
def sleep(x):
time.sleep(x)
sleep(3)
# 结果
call sleep took 0:00:03.003540
这里将打印直接变成了匿名函数放到了参数里面。这样打印要更加的灵活。
(后续没太明白,等回滚一遍了再来)
小结
装饰器是一个参数是函数,返回值也是函数的参数。
调用的时候是一个个作为参数传进去的过程。