函数、方法或类定义的前面可以使用一个特殊的符号,称为装饰器,其目的是修改定义后面的行为。装饰器使用 @ 符号表示,必须放在单独的行上并且位于对应的函数、方法或类之前,例如:
class Foo():
@staticmethod
def bar():
pass
也可以使用多个装饰器,但每个装饰器必须位于不同的行上,例如:
@foo
@bar
def spam():
pass
装饰器本质上是一个函数,其主要用途是包装另一个函数或者类。这种包装的首要目的是透明地修改或增强被包装对象的行为。表示装饰器的语法是特殊符号@。 如下所示:
@trace
def square(x):
return x*x
上面的代码可以等效为:
def square(x):
return x*x
square = trace(square)
上面的例子中定义了函数square(), 但是在定义后,函数对象本身就立即被传递给函数trace(),后者返回一个对象替代原始的square。现在,让我们考虑trace的实现,从而说明这样做的好处:
enable_tracing = True
if enable_tracing:
debug_log = open('debug.log', 'w')
def trace(func):
if enable_tracing:
def callf(*args, **kwargs):
debug_log.write(f'calling {func.__name__} : {args}, {kwargs}')
r = func(*args, **kwargs)
debug_log.write(f'{func.__name__} returned {r}')
return r
else:
return func
这段代码中,trace()创建了写有一些调试输出的包装器函数,然后调用了原始的函数对象。因此如果调用square()函数,将会看到包装器函数中write方法的输出。trace()函数返回的函数callf是一个闭包,用于替换原始的函数。另外,trace功能本身只能像上面这样使用全局的enable_tracing来启用,如果把enable_tracing设为False,那么trace()装饰器只是返回未修改的原始函数。因此,禁用trace时,使用装饰器不会增加性能上的负担。
使用装饰器时,它们必须出现在函数或类定义的上方,且位于单独的行上。可以同时使用多个装饰器,当一个函数被多个装饰器装饰时,装饰器的加载顺序是从下往上的(从内到外的)。例如:
@foo
@bar
@spam
def grok(x):
pass
在这个例子中,装饰器将按照它们出现的先后顺序应用,结果是:
def grok(x):
pass
grok = foo(bar(spam(grok)))
装饰器也可以接收参数, 例如:
@eventhandler('BUTTON')
def handle_button(msg):
pass
@eventhandler('RESET')
def handle_reset(msg)
pass
如果提供参数,装饰器的语义如下所示:
def handle_button(msg):
pass
temp = eventhandler('BUTTON') # 使用提供的参数调用装饰器
handle_button = temp(handle_button) # 使用装饰器返回的函数
事件处理程序装饰器示意:
event_handlers = {}
def eventhandler(event):
def register_function(f):
event_handlers[event] = f
return f
return register_function
有时候,我们需要在定义类之后执行一些额外处理,例如将类添加到注册表或者数据库。这类问题的一个解决方案就是使用类装饰器。类装饰器是一种函数, 它接受类作为输入并返回类作为输出。
对于类装饰器,应该让装饰器函数始终返回类对象作为结果。
看如下的例子:
registry = {}
def register(cls):
registry[cls.__clsid__] = cls
return cls
在这个例子中,注册函数在类中查找__clsid__属性。如果找到,则使用该属性将类添加到字典中,将类标识符映射到类对象。要使用该函数,可以在类定义前将它作为装饰器,例如:
@register
class Foo():
__clsid__ = '123-456'
def bar(self):
pass
可以看到,使用装饰器语法可以带来极大的便利。
当然了,你也可以通过另一种方式来做到:
class Foo():
__clsid__ = '123-456'
def bar(self):
pass
register(Foo)
尽管可以在类装饰器函数中对类做很多神奇的事情,但是最好避免执行过于花哨的操作,例如为类添加一个包装器或者重写类内容。
小手一抖,点个赞再走哦~~~