共计 5482 个字符,预计需要花费 14 分钟才能阅读完成。
引入装璜器
如果想在一个函数执行前后执行一些别的代码,比方打印一点日志用来输入这个函数的调用状况那应该怎么做呢?
#!/usr/bin/env python
# coding=utf-8
def logger(fn): # 函数作为参数即 fn 能够为任何参数
def wrap(*args, **kwargs): # 可变参数 args 和 kwargs
print('call {}'.format(fn.__name__))
ret = fn(*args, **kwargs) # 函数调用时的参数解构
print('{} called'.format(fn.__name__))
return ret # 返回函数的返回值
return wrap
def add(x, y):
return x + y
logger_add = logger(add)
print(logger_add.__name__)
print(logger_add)
ret = logger_add(3, 5)
print(ret)
#输入后果:wrap
<function logger.<locals>.wrap at 0x7fba35f4fe18>
call add
add called
8
<!–more–>
也能够用以下形式来实现这种成果
@logger
def add(x, y):
return x + y ret = add(3, 5)
print(ret)
# 输入后果:call add
add called
8
这就是 Python 装璜器的一个简略应用
什么是装璜器?
装璜器是用于软件设计模式的名称。装璜器能够动静地扭转函数,办法或类的性能,而不用间接应用子类或扭转被装璜的函数的源代码。Python 装璜器是对 Python 语法的一种非凡扭转,它容许咱们更不便地批改函数,办法以及类。
当咱们依照以下形式编写代码时:
@logger
def add(x, y):
...
和独自执行上面的步骤是一样的:
def add(x, y):
...
logger_add = logger(add)
装璜器外部的代码个别会创立一个新的函数,利用 *args
和**kwargs
来承受任意的参数,上述代码中的 wrap()函数就是这样的。在这个函数外部,咱们须要调用原来的输出函数(即被包装的函数,它是装璜器的输出参数)并返回它的后果。然而也能够增加任何想要增加的代码,比方在上述代码中输入函数的调用状况,也能够增加计时解决等等。这个新创建的 wrap 函数会作为装璜器的后果返回,取代了原来的函数。
所以 在 Python 中,装璜器的参数是一个函数,返回值是一个函数的函数。
装璜器的示例:计时解决
写一个装璜器,用来计算一个函数的执行工夫
import time
def timethis(fn):
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
print(fn.__name__, end - start)
return ret
return wrap
如果要对 add 函数计时:
@timethis
def add(x, y):
return x + y
ret = add(3, 5)
print(ret)
# 输入后果
add 1.9073486328125e-06
8
如果要对 sleep 函数计时:
@timethis
def sleep(x):
time.sleep(x)
sleep(3)
# 输入后果
sleep 3.003262519836426
保留被装璜函数的元信息
什么是函数的元信息
比方装璜器的名称,装璜器的 doc 等等。咱们能够应用 dir 函数列出函数的所有元信息:dir(sleep)
,输入后果如下
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
能够看到有很多的元信息,咱们比拟罕用的是 __name__
和__doc__
这两个属性 \
而且 __doc__
属性也就是函数的文档信息,能够通过 help 函数查看失去
为什么要保留被装璜函数的元信息
改写 装璜器的利用 1:计时解决 中的 sleep 函数如下:
@timeit
def sleep(x):
'''This function is sleep.'''
time.sleep(x)
sleep(3)
print(sleep.__name__)
print(sleep.__doc__)
以上代码输入后果如下:
3.0032713413238525
wrap
None
能够发现 sleep 函数的 __name__
是 wrap,而不是 sleep,而 __doc__
属性为空,而不是 sleep 函数的 docstring。也就是说 通过装璜器装璜过后的函数的元信息产生了扭转,这时候如果程序须要函数的元信息,那么就有问题了。
如何保留被装璜函数的元信息
计划 1:手动给被装璜函数的元信息赋值
以 __name__
和__doc__
这两个属性为例
import time
def timeit(fn):
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
print(end - start)
return ret
wrap.__doc__ = fn.__doc__ # 手动赋值__doc__信息
wrap.__name__ = fn.__name__ # 手动赋值__name__信息
return wrap
@timeit
def sleep(x):
'''This function is sleep.'''
time.sleep(x)
if __name__ == "__main__":
sleep(3)
# print(dir(sleep))
print(sleep.__name__)
print(sleep.__doc__)
输入后果如下
3.004547119140625
sleep
This function is sleep.
能够发现,__name__
和 __doc__
这两个属性的确赋值胜利了。
咱们能够将元信息赋值的过程改写为函数,如下
import time
def copy_properties(src, dst): # 将元信息赋值的过程改成函数 copy_properties
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
def timeit(fn):
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
print(end - start)
return ret
copy_properties(fn, wrap) # 调用 copy_properties 函数批改元信息
return wrap
@timeit
def sleep(x):
'''This function is sleep.'''
time.sleep(x)
if __name__ == "__main__":
sleep(3)
# print(dir(sleep))
print(sleep.__name__)
print(sleep.__doc__)
这样批改后,同样能够解决问题。
持续批改 copy_properties 函数,使得 copy_properties 能够返回一个函数
def copy_properties(src):
def _copy(dst): # 内置一个_copy 函数便于返回
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return _copy
def timeit(fn):
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
print(end - start)
return ret
copy_properties(fn)(wrap) # 调用 copy_properties 函数
return wrap
同样能够问题。
如果持续批改 copy_properties 函数,使得_copy 函数是一个装璜器,传入 dst,返回 dst,批改如下:
def copy_properties(src): # 先固定 dst,传入 src
def _copy(dst): # 传入 dst
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst # 返回 dst
return _copy # 返回一个装璜器
def timeit(fn):
@copy_properties(fn) # 带参数装璜器的应用办法
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
print(end - start)
return ret
return wrap
copy_properties 在此处返回一个带参数的装璜器,因而能够间接依照装璜器的应用办法来装璜 wrap 函数,这个批改 copy_properties 函数的过程称为函数的柯里化。
计划 2:应用 functools 库的 @wraps 装璜器
functools 库的 @wraps 装璜器实质上就是 copy_properties 函数的高级版本:蕴含更多的函数元信息。首先查看 wrap 装璜器的帮忙信息:
import functools
help(functools.wraps)
wrap 装璜器函数的原型是:
wraps(wrapped, assigned=('module', 'name', 'qualname', 'doc', 'annotations'), updated=('dict',))
所以这个装璜器会复制 module 等元信息,然而也不是所有的元信息,并且会更新 dict。
应用示例如下:
import time
import functools
def timeit(fn):
@functools.wraps(fn) # wraps 装璜器的应用
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
print(end - start)
return ret
return wrap
def sleep(x):
time.sleep(x)
print(sleep.__name__)
print(sleep.__doc__)
编写一个带参数的装璜器
如果上述的 timeit 装璜器,咱们须要输入执行工夫超过若干秒(比方一秒)的函数的名称和执行工夫,那么就须要给装璜器传入一个参数 s,示意传入的工夫距离,默认为 1s。
咱们能够给写好的装璜器外面包一个函数 timeitS,工夫距离 s 作为这个函数的参数传入,并且对内层的函数可见,而后这个函数返回写好的装璜器。
import time
import functools
def timeitS(s):
def timeit(fn):
@functools.wraps(fn)
def wrap(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
end = time.time()
if end - start > s:
print('call {} takes {}s'.format(fn.__name__, end - start))
else:
print('call {} takes {}s less than {}'.format(fn.__name__, end - start, s))
return ret
return wrap
return timeit
@timeitS(2)
def sleep(x):
time.sleep(x)
sleep(3)
sleep(1)
输入后果如下:
call sleep takes 3.001342535018921s
call sleep takes 1.000471830368042s less than 2
所以,咱们能够将带参数的装璜器了解为:
- 带参数的装璜器就是一个函数,这个函数返回一个不带参数的装璜器
记得帮我点赞哦!
精心整顿了计算机各个方向的从入门、进阶、实战的视频课程和电子书,依照目录正当分类,总能找到你须要的学习材料,还在等什么?快去关注下载吧!!!
朝思暮想,必有回响,小伙伴们帮我点个赞吧,非常感谢。
我是职场亮哥,YY 高级软件工程师、四年工作教训,回绝咸鱼争当龙头的斜杠程序员。
听我说,提高多,程序人生一把梭
如果有幸能帮到你,请帮我点个【赞】,给个关注,如果能顺带评论给个激励,将不胜感激。
职场亮哥文章列表:更多文章
自己所有文章、答复都与版权保护平台有单干,著作权归职场亮哥所有,未经受权,转载必究!