装饰器
装饰器是一个可以接收调用也可以返回调用的调用。装饰器就是一个函数(或调用,如有_call_method_方法的对象),该函数接收被装饰的函数作为其位置参数。装饰器通过使用该参数来执行某些操作,然后返回原始参数或一些其他的调用(大概以这种方式与装饰器交互)。
由于函数在Python中是一级对象,因此它们能够像其他对象一样被传递到另一个函数。装饰器就是接收另一个函数作为参数,并用其完成一些操作的函数。
>>> def decorate(func):
func.__doc__ += '\nDecorated by decorate.' #函数的docstring是在第一行指定的字符串
return func
>>> def add(x, y):
'''Return the sum of x and y''' #docstring
return x + y
>>> help(add)
Help on function add in module __main__:
add(x, y)
Return the sum of x and y
>>> add = decorate(add)
>>> help(add)
Help on function add in module __main__:
add(x, y)
Return the sum of x and y
Decorated by decorate.
语法:
假设该装饰器有一个单独的位置参数,而这个参数是装饰过的方法:
def decorate(func):
func.__doc__ += '\nDecorated by decorate.'
return func
@decorate #被装饰函数作为位置参数自动传入装饰器中.
def sum(x, y):
'''Return the sum of x and y.'''
return x + y
装饰器应用的顺序:
使用@语法时,在创建被装饰的可调用函数后,会立刻应用装饰器。
对某个可调用函数,可以使用多个装饰器(就像可以多次封装函数调用一样)。但请注意,如果通过@语法使用多个装饰器,就需要按照自底向上的顺序来应用它们,很多时候,顺序非常重要:
>>> def decorate_1(func):
print('Decorated by decorate_1.')
return func
>>> def decorate_2(func):
print('Decorated by decorate_2.')
return func
>>> @decorate_1
@decorate_2
def add(x, y):
return x + y
Decorated by decorate_2. #先应用decorate_2.
Decorated by decorate_1.
前面的示例与下面的代码在语法上是相同的:
装饰器的应用是自底向上的,与函数的解析(由内向外)是相同的。首先创建add函数,然后,调用装饰器@decorate_2,并将其作为add函数的装饰方法,返回自己的可调用函数(add的修改版本),发送给装饰器decorate_1。
>>> add = decorate_1(decorate_2(add))
Decorated by decorate_2.
Decorated by decorate_1.
编写装饰器的时机:
(1)附加功能:
在执行被装饰方法之前或之后添加额外的功能。这可能包括检查身份、将函数结果记录到固定位置等用例。
(2)数据的清理或添加:
装饰器也可以清理传递给被装饰函数的参数的值,从而确保参数类型的一致性或使该值符合指定的模式。
装饰器也可以改变或清除从函数中返回的数据。如果想让函数返回一个原生的Python对象(如列表或字典),但是最终在另一端接收到序列化格式的数据(如JSON),这将是很有价值的用例。
有些装饰器实际上为函数提供了额外的数据,通常这些数据是附加参数的形式。
(3)函数注册:
很多时候,在其他位置注册函数很有用。例如,在任务运行器中注册一个任务,或者注册一个带有信号的处理器的函数。任何由外部输入或路由机制决定函数运行的系统都可以使用函数注册。
编写装饰器:
装饰器仅仅是这样的函数:接收被装饰的可调用函数作为唯一参数,并且返回一个可调用函数。
一个很主要的注意事项是:当装饰器应用到装饰函数时(而不是调用装饰器时),会执行装饰代码本身。
例:函数注册表
>>> registry = []
>>> def register(decorated):
registry.append(decorated)
return decorated
>>> @register
def foo():
return 3
>>> @register
def bar():
return 5
>>> answers = []
>>> for func in registry:
answers.append(func())
>>> answers
[3, 5]
>>> class Registry:
def __init__(self):
self._functions = []
def register(self, decorated): #装饰器
self._functions.append(decorated)
return decorated
def run_all(self, *args, **kwargs):
return_values = []
for func in self._functions:
return_values.append(func(*args, **kwargs))
return return_values
>>> a = Registry()
>>> b = Registry()
>>> @a.register
def foo(x = 3):
return x
>>> @b.register
def bar(x = 5):
return x
>>> @a.register
@b.register
def baz(x = 7):
return x
>>> a.run_all()
[3, 7]
>>> b.run_all()
[5, 7]
>>> a.run_all(x = 4)
[4, 4]
>>> a.run_all(9)
[9, 9]
例:执行时封装代码
例1:类型检查
>>> def requires_ints(decorated):
def inner(*args, **kwargs):
kwarg_values = [i for i in kwargs.values()]
for arg in list(args) + kwarg_values:
if not isinstance(arg, int):
raise TypeError('%s only accepts integers as arguments.' % decorated.__name__)
return decorated(*args, **kwargs)
return inner
>>> @requires_ints
def foo(x, y):
'''Return the sum of x and y.'''
return x + y
>>> help(foo)
Help on function inner in module __main__:
inner(*args, **kwargs)
>>> foo(4, 9)
13
>>> foo(2, '123')
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
foo(2, '123')
File "<pyshell#35>", line 6, in inner
raise TypeError('%s only accepts integers as arguments.' % decorated.__name__)
TypeError: foo only accepts integers as arguments.
装饰器自身是requires_ints。它接收一个参数:decorated,即被装饰的可调用函数。装饰器唯一的做的事情就是返回一个新的可调用函数,即本地函数 inner。该函数替代了被装饰方法。
例2:保存帮助信息
>>> import functools
>>> def require_ints(decorated):
@functools.wraps(decorated)
def inner(*args, **kwargs):
kwargs_values = [i for i in kwargs.values()]
for arg in args + kwargs_values:
if not isinstance(i, int):
raise TypeError('%s only accepts integers as arguments.' % decorated.__name__)
return decorated(*args, **kwargs)
return inner
>>> @require_ints
def foo(x, y):
'''Return the sum of x and y'''
return x + y
>>> help(foo)
Help on function foo in module __main__:
foo(x, y)
Return the sum of x and y
Python实现一个名为@functools.wraps的装饰器,将一个函数中的重要内部元素复制到另一个函数。
例3:用户验证
>>> class User:
def __init__(self, username, email):
self.username = username
self.email =email
>>> class AnonymousUser(User):
def __init__(self):
self.username = None
self.email = None
def __nonzero__(self):
return False
>>> import functools
>>> def require_user(func):
@functools.wraps(func)
def inner(user, *args, **kwargs):
if user and isinstance(user, User):
return func(user, *args, **kwargs)
else:
raise ValueError('A valid user is required to run this.')
return inner
注意:该装饰器仅仅能够正确包装函数或静态方法,如果包装绑定到类的方法,将会失败。这是由于装饰器忽略了将self发送给绑定方法作为第一个参数的期望。
例4-1:输出格式化
>>> import functools
>>> import json
>>> def json_output(decorated):
@functools.wraps(decorated)
def inner(*args, **kwargs):
result = decorated(*args, **kwargs)
return json.dumps(result)
return inner
>>> @json_output
def do_nothing():
return {'name': 'Tom'}
>>> do_nothing()
'{"name": "Tom"}'
例4-2:捕获特定异常并以指定格式的JSON输出
>>> import functools
>>> import json
>>> class JSONOutputError(Exception):
def __init__(self, message):
self._message = message
def __str__(self):
return self._message
>>> def json_output(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)
return inner
>>> @json_output
def error():
raise JSONOutputError('This function is erratic.')
>>> error()
'{"status": "error", "message": "This function is erratic."}'
注意:只有JSONOutputError异常类(及其子类)可以接收此特殊处理任何其他异常将被正常传递,并且生成一个回溯。
>>> @json_output
def other_error():
raise ValueError('Raise ValueError...')
>>> other_error()
Traceback (most recent call last):
File "<pyshell#28>", line 1, in <module>
other_error()
File "<pyshell#18>", line 5, in inner
result = decorated(*args, **kwargs)
File "<pyshell#27>", line 3, in other_error
raise ValueError('Raise ValueError...')
ValueError: Raise ValueError...
例5:日志管理
>>> import functools
>>> import logging
>>> import time
>>> def logged(method):
@functools.wraps(method)
def inner(*args, **kwargs):
start = time.time()
return_value = method(*args, **kwargs)
end = time.time()
delta = end - start
logger = logging.getLogger('decorated.logged')
logger.warn('Called method: %s at %0.2f; execution time %0.2f seconds; result %r.' % (method.__name__, start, delta, return_value))
return return_value
return inner
>>> @logged
def sleep_and_return(return_value):
time.sleep(2)
return return_value
>>> sleep_and_return(42)
Called method: sleep_and_return at 1538036038.34; execution time 2.00 seconds; result 42.
42
注意:永远不要期望既可以接收被装饰的方法又可以接收关键字参数,因为装饰器在被调用过程中只能接收被调用方法作为唯一参数。
二、装饰类:
本质上来说装饰器是一个接收可调用函数的可调用函数,并返回一个可调用函数。这意味着装饰器可以被用于装饰函数和类(类本身也是可调用函数)。
装饰类有多种用途,类装饰器可以与被装饰类的属性交互。类装饰器可以添加属性或将属性参数化。
例:考虑一个类的功能,每个实例都知道自己被实例化的时间,并按照创建时间排序
>>> import functools
>>> import time
>>> def sort_by_create_time(cls):
original_init = cls.__init__
@functools.wraps(original_init)
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self._created = time.time()
cls.__init__ = new_init
cls.__lt__ = lambda self, other: self._created < other._created
cls.__gt__ = lambda self, other: self._created > other._created
return cls
>>> @sort_by_create_time
class Sortable:
def __init__(self, identifier):
self.identifier = identifier
def __repr__(self):
return self.identifier
>>> first = Sortable('first')
>>> second = Sortable('second')
>>> third = Sortable('third')
>>> sortables = [second, first, third]
>>> sorted(sortables)
[first, second, third]
>>> sorted(sortables, reverse = True)
[third, second, first]
注意:如果应用 @sort_by_create_time装饰器的类定义了自己的__lt__和__gt__方法,那么该装饰器将会重写这两个方法。
如果类没有识别出_created属性被用于排序,那么该属性值自身并没有什么用处。因此,装饰器还加入了__lt__与__gt__魔术方法。这使and操作符基于这些方法的结果返回True或False。这同时也影响了sorted函数和其他类似函数的行为。