日志记录Logging
执行时包装代码的最后一个例子是一个通用的日志记录函数。 考虑下面引起函数调用的装饰器, 运行时间,
结果会被记录:
import functools
import logging
import time
def logged(method):
"""Cause the decorated method to be run and its results
logged, along
with some other diagnostic information.
"""
@functools.wraps(method)
def inner(*args, **kwargs):
#Record our start time.
start = time.time()
#Run the decorated method.
return_value = method(*args, **kwargs)
#Record our completion time, and calculate the delta.
end = time.time()
delta = end - start
#Log the method call and the result.
logger = logging.getLogger('decorator.logged')
logger.warn('Called method %s at %.02f; execution time
%.02f seconds; result %r.' %
(method.__name__, start, delta, return_value))
#Return the methods original return value.
return return_value
return inner
当应用到一个函数上后,这个装饰器正常地运行那个函数,但函数调用结束后会使用Python logging模块记录信息。
>>> import time
>>> @logged… def sleep_and_return(return_value):
... time.sleep(2)
... return return_value…
>>>
>>> sleep_and_return(42)
Called method sleep_and_return at 1424462194.70;
execution time 2.00 seconds; result 42.
42
不像先前的例子,这个装饰器不显式地更改函数调用. 不存在你应用这个装饰器后获得的结果与没有被装饰的函数的结果不一样的情况。这个装饰器做了些幕后工作,但并不改变实际结果。
值得注意的是, @json_output 和 @logged 装饰器都提供 inner
函数 ,这个函数简单地以最小的侦测采用和传递可变参数和关键字参数。
这是一种重要的模式。一种它尤其重要的方式是,许多装饰器可能被用来装饰纯粹的函数和类的方法。记住,在Python中,类中声明的方法会获得一个额外位置参数,即广为人知的self。当装饰器在使用时,它不会改变 (这就是为什么先前的requires_user装饰器在类的绑定方法上不起作用)
例如@json_result被用来装饰一个类的方法,inner函数被调用,它接收一个类的实例作为第一个参数。实际上这没有问题。在这种情况下,这个参数就是args[0],它被传送给被装饰方法。
装饰器参数
到目前为止列出的所有装饰器都没有任何参数。作为讨论过的内容,有一个暗含的参数–被装饰的方法。然而,有时让装饰器自身使用一些它需要的信息去装饰相关方法会有用处。
一个参数传给一个装饰器和一个参数传给一个正在调用的方法之间的不同是,当一个函数被声明并被装饰,传给装饰器的参数会被立刻处理。相反,传给函数的参数在函数调用时被处理。通过@functools.wraps的多次使用,你已经看到了一个参数传给装饰器的例子。它使用一个参数—-被包装的方法,方法的help和docstring等类似的东西应该被保留。然而,装饰器有内含的调用签名。他们使用一个位置参数–被装饰的方法。所以,这是怎么工作的?答案说来就复杂了。
回想运行时包装代码的基本装饰器 ,他们在局部范围声明了一个 inner 方法 然后返回它. 这就是由装饰器返回的可调用对象. 它被指派了被调用函数的名字. 使用参数的装饰器多添加一个包装层,这是因为,使用参数的装饰器不再是一个实际的装饰器。它是一个返回装饰器的函数,是一个使用一个参数(被装饰的方法)的函数。然后装饰函数并返回一个可调用对象。听起来混乱,考虑下面的例子,在这里,装饰器@json_output的功能被增强了,要求缩进和排序:
import json
class JSONOutputError(Exception):
def __init__(self, message):
self._message = message
def __str__(self):
return self._message
def json_output(indent=None, sort_keys=False):
"""Run the decorated function, serialize the result of
that function
to JSON, and return the JSON string.
"""
def actual_decorator(decorated):
@functools.wraps(decorated)
def inner(*args, **kwargs):
try:
result = decorated(*args, **kwargs)
except JSONOutputError as ex:
result = {
'status': 'error',
'message': str(ex),
}
return json.dumps(result, indent=indent,
sort_keys=sort_keys)
return inner
return actual_decorator
那么,发生了什么,为什么这回起作用?这是一个函数,json_output,接收两个参数(indent 和 sort_keys). 它返回另一个函数, 叫 actual_decorator, 这是 (如同名字表名的) 要作为装饰器使用的. 这是一个典型的装饰器—一个接收可调用对象(被装饰的)做参数的可调用对象,并且返回一个可调用对象(inner).
注意函数已经有所改变来容纳indent和sort_keys参数。
inner 函数是最终使用 indent 和 sort_keys
参数的. 这没有问题,因为Python的块作用域规则允许这样。使用不同的indent和sort_keys来调用也不成问题,因为inner是本地函数(每次装饰器被使用都会返回一个不同的副本)
应用 json_output 函数:
@json_output(indent=4)
def do_nothing():
return {'status': 'done'}
现在运行do_nothing , 会产生一个带缩进的JSON:
>>> do_nothing()
'{\n "status": "done"\n}'
这是怎么起作用的?
但是,等一等. 如果json_output 不是一个装饰器, 而是一个返回装饰器的函数,为什么它使用起来看着像是一个装饰器?在这里,Python解释器做了什么来让它工作的?更多的解释已经就绪。在这的关键是操作顺序。
特别地,函数调用(json_output(indent=4)) 先于装饰器应用语法(@)被处理 。因此,函数调用的结果会应用给装饰器。
发生的第一件事情是解释器寻找 json_output 函数调用,然后解析这个调用:
@json_output(indent=4)
def do_nothing():
return {'status': 'done'}
json_output 函数所要做的一切就是定义另一个函数, actual_decorator,
然后返回它. 这个函数的结果会提供给@,像下面这样:
@actual_decorator
def do_nothing():
return {'status': 'done'}
现在, actual_decorator 在运行. 它声明另一个本地函数, inner, 并返回它. 像先前讨论过的,这个函数会被指派名字
do_nothing, 被装饰方法的名字. 当do_nothing被调用,
inner 函数就会被调用, 运行被装饰方法, JSON使用合适缩进 调用dumps 处理结果
调用签名很重要
当你引进了你的新的,更改过后的json_output函数,你实际引进了一个反向不兼容(backward-incompatible )的改变,意识到这点很重要。
为什么?因为现在期待一个额外的函数调用。如果你想要旧的json_output的行为,不需要任何可用的参数的值,你仍然必须调用这个方法
换句话说,你必须像下面这样做:
@json_output()
def do_nothing():
return {'status': 'done'}
注意圆括号. 它们有影响,因为它们指出了函数正在被调用(即便没有参数),然后结果应用给@.
前面的代码不等价于下面:
@json_output
def do_nothing():
return {'status': 'done'}
这呈现出两个问题。有点让人迷惑,如果你习惯于看到不带签名的装饰器的应用,提供一个空签名的需要就违背直觉。
第二,如果旧的装饰器在你的应用中已经存在,你必须返回并编辑所有它们的现有的调用。如果可能的话,你应该避免反向不减容(backward-incompatible)改变。
完美的情况下,下面三种不同的使用方式,装饰器都会工作
@json_output
@json_output()
@json_output(indent=4)
让装饰器基于接收到的参数来改变它的行为是可能的。记住,装饰器只是一个函数,拥有任何其它函数所拥有的所有灵活性,包括对它获取到的输入做出需要做出的响应。
考虑这个对 json_output的更加灵活的迭代:
import functools
import json
class JSONOutputError(Exception):
def __init__(self, message):
self._message = message
def __str__(self):
return self._message
def json_output(decorated_=None, indent=None, sort_keys=False):
"""Run the decorated function, serialize the result of that function
to JSON, and return the JSON string.
"""
# Did we get both a decorated method and keyword arguments?
# That should not happen.
if decorated_ and (indent or sort_keys):
raise RuntimeError('Unexpected arguments.')
# Define the actual decorator function.
def actual_decorator(func):
@functools.wraps(func)
def inner(*args, **kwargs):
try:
result = func(*args, **kwargs)
except JSONOutputError as ex:
result = {
'status': 'error',
'message': str(ex),
}
return json.dumps(result, indent=indent,
sort_keys=sort_keys)
return inner
#Return either the actual decorator, or the result of applying
#the actual decorator, depending on what arguments we got.
if decorated_:
return actual_decorator(decorated_)
else:
return actual_decorator
在目前是不是正作为装饰器使用这一方面,这个函数正努力变得智能。
首先,它确保它不会以出乎意料的方式被调用
你永远不要期待接收被装饰方法同时关键值参数,因为装饰器被调用时总是以被装饰方法作为唯一参数。
第二,它定义了actual_decorator函数,这是要被返回和应用的实际装饰器。它定义了inner 函数,它时从装饰器中返回的最终函数。
最终, 它返回合适结果,这基于它被如何调用:
如果 设置了decorated_ , 它会被作为纯粹的装饰器调用, 没有方法签名,然后它的响应应用给最终装饰器并返回inner函数 . 再次注意使用参数的装饰器如何实际地运作。首先, actual_decorator(decorated_)被调用,解析。然后它的结果(必须是一个可调用对象,因为这是一个装饰器)被调用,inner被提供作为唯一的参数。
如果decorated_没被设置,就会使用关键字参数调用,这个函数必须返回一个实际的装饰器,它接收被装饰方法,并返回inner。因此,这个函数返回actual_decorator
然后这会被python解释器作为实际装饰器(最终返回inner)
为何这个技术有价值?它让你能够先先前使用过的一样管理你的装饰器的功能。意味着你不用去更新已经应用了装饰器的每个地方,但仍然获得了在你需要时添加参数的灵活性。