装饰器(上)
给函数“穿衣服”
import time
from functools import wraps
def timethis(function):
@wraps(function)
def wrapper(*args, **kwargs):
start = time.time()
function(*args, **kwargs)
print(function.__name__, start - time.time())
return wrapper
@timethis
def downout(n):
while n > 0:
n -= 1
downout(10000)
//这是Python,但是我喜欢用'//'在文章里面注释
// 如果不用@timethis,可以使用如下方式,达到等价效果。
downout = timethis(downout)
downout(10000)
编写装饰器时如何保存函数的元数据
什么叫函数的元数据呢,其实就是和函数一些相关的属性,比如函数的name(函数名字),doc(函数文档),dict(属性字典)等等和函数相关的。其实
from functools import wraps
@wraps(function)
这个装饰器就是用来保存函数的元数据,在这个装饰器的作用下,函数的所有元数据会被保存,你可以试试,如果去掉@wraps(function)
那么metadata
函数的元数据,将会丢失。
定义一个带参数的装饰器
可以接受一些 参数,虽然看起来有点复杂, 核心却是很简单的。最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数上面。 内层的函数 decorate() 接受一个函数作为参数,然后在函数上面放置一个包装器。 这里的关键点是包装器是可以使用传递给 logged() 的参数的。
import logging
import os
import time
from functools import wraps
def logged(level, name=None, message=None):
"""
:param level:日志记录级别
:param name:记录器名称
:param message:日志消息
"""
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
meg = f"[level:{level}] {os.path.basename(__file__)} {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} {logmsg}"
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, meg)
return func(*args, **kwargs)
return wrapper
return decorate
# Example use
@logged(logging.WARNING)
def add(x, y):
return x + y
@logged(logging.CRITICAL, 'example')
def spam():
print('Spam!')
可自定义属性的装饰器
from functools import wraps, partial
import logging
def attach_wrapper(func_obj, func=None):
"""将函数附加为obj的属性"""
if func is None:
return partial(attach_wrapper, func_obj)
setattr(func_obj, func.__name__, func)
return func
def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[level:{level}]\tname:{logname}\tmessage:{logmsg}")
return func(*args, **kwargs)
# Attach setter functions
@attach_wrapper(wrapper)
def set_level(newlevel):
nonlocal level
level = newlevel
@attach_wrapper(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg
return wrapper
return decorate
# Example use
@logged(logging.DEBUG)
def add(x, y):
return x + y
def spam(x, y):
return x + y
// -----一下是功能演示-------------------
print("---Example use fun__add__")
add(3, 2)
add.set_message("Alex")
add(3, 2)
// 等价调用
print("---Example use fun__spam__")
// spam -> wrapper 包含闭包内的局部变量,以及方法
spam = logged(level=25)(spam)
spam(3, 2)
spam.set_message("Alex is good girl")
spam(3, 2)
带可选参数的装饰器
从示例中可以看到,现在这个装饰器既能够以简单的形式(即@logged)使用,也可以提供可选的参数给它(即,@logged(level=50, name='example'))。
提到的实际上是一种编程一致性(programming consistency)的问题。当使用装饰器时,大部分程序员习惯于完全不使用任何参数,或者就像示例中那样使用参数。从技术上来说,如果装饰器的所有参数都是可选的,那么可以像这样来使用:
@logged() # 括号可以带,或者不带
,
from functools import wraps, partial
def logged(func=None, *, level=50, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
logname = name if name else func.__module__
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[level:{level}]\tname:{logname}\tmessage:{logmsg}")
return func(*args, **kwargs)
return wrapper
利用装饰器强制函数上的类型检查
为函数参数添加强制性的类型检查功能,将其作为一种断言或者与调用者之间的契约。
装饰器的一个特性就是它们只会在函数定义的时候应用一次。在某些情况下,我们可能想禁止由装饰器添加的功能。为了做到这点,只要让装饰器函数返回那个未经过包装的函数即可。在解决方案中,如果全局变量__debug__被设为False,下列代码就会返回未修改过的函数(当Python解释器以-O或-OO的优化模式执行的话,则属于这种情况)。接下来,编写这个装饰器比较棘手的地方在于要涉及对被包装函数的参数签名做检查。在这里,我们可选择的工具应该是
inspect.signature()
函数。
from inspect import signature
from functools import wraps
def typea_ssert(*ty_args, **ty_kwargs):
def decorate(func):
# If in optimized mode, disable type checking
if not __debug__:
return func
# Map function argument names to supplied types
sig = signature(func)
# 对参数进行绑定
bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments #-> OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
@wraps(func)
def wrapper(*args, **kwargs):
bound_values = sig.bind(*args, **kwargs) # -> <BoundArguments (x=1, y=2, z=3)>
# Enforce type assertions across supplied arguments
for name, value in bound_values.arguments.items():
if name in bound_types:
if not isinstance(value, bound_types[name]):
raise TypeError(
'Argument {} must be {}'.format(name, bound_types[name])
)
return func(*args, **kwargs)
return wrapper
return decorate
# 我们会发现这个装饰器相当灵活,既允许指定函数参数的所有类型,也可以只指定一部分子集。
# 此外,类型既可以通过位置参数来指定,也可以通过关键字参数来指定。
@typea_ssert(int, z=int)
def spam(x, y, z=42):
pass
spam(1, 2, 3) # Success
spam(1, "Alex", "bye") # Error