说每种方法是否有“优点”是相当主观的。
但是,对引擎盖下的内容有一个很好的了解将使人们很自然地为每个场合选择最佳选择。
装饰器(谈论函数装饰器)只是一个以函数作为输入参数的可调用对象。Python 有其相当有趣的设计,它允许人们创建除函数之外的其他类型的可调用对象——并且有时可以将其用于创建更可维护或更短的代码。
在 Python 2.3 中添加了装饰器作为“语法快捷方式”
def a(x):
...
a = my_decorator(a)
除此之外,我们通常将装饰器称为一些“可调用对象”,而不是“装饰器工厂”——当我们使用这种类型时:
@my_decorator(param1, param2)
def my_func(...):
...
使用 param1 和 param2 对“my_decorator”进行调用 - 然后返回一个将再次调用的对象,这次将“my_func”作为参数。因此,在这种情况下,从技术上讲,“装饰器”是“my_decorator”返回的任何内容,使其成为“装饰器工厂”。
现在,描述的装饰器或“装饰器工厂”通常必须保持一些内部状态。在第一种情况下,它唯一保留的是对原始函数的引用(f
在您的示例中调用的变量)。“装饰工厂”可能想要注册额外的状态变量(上例中的“param1”和“param2”)。
在作为函数编写的装饰器的情况下,这种额外状态保存在封闭函数内的变量中,并由实际的包装函数作为“非本地”变量访问。如果编写了一个适当的类,它们可以作为实例变量保存在装饰器函数中(这将被视为“可调用对象”,而不是“函数”) - 并且对它们的访问更加明确且更具可读性。
因此,在大多数情况下,您是否喜欢一种方法或另一种方法取决于可读性:对于简短的简单装饰器,函数式方法通常比作为类编写的方法更具可读性 - 而有时更复杂的方法 - 尤其是一种“装饰工厂”将充分利用 Python 编码前的“平面优于嵌套”建议。
考虑:
def my_dec_factory(param1, param2):
...
...
def real_decorator(func):
...
def wraper_func(*args, **kwargs):
...
#use param1
result = func(*args, **kwargs)
#use param2
return result
return wraper_func
return real_decorator
针对这种“混合”解决方案:
class MyDecorator(object):
"""Decorator example mixing class and function definitions."""
def __init__(self, func, param1, param2):
self.func = func
self.param1, self.param2 = param1, param2
def __call__(self, *args, **kwargs):
...
#use self.param1
result = self.func(*args, **kwargs)
#use self.param2
return result
def my_dec_factory(param1, param2):
def decorator(func):
return MyDecorator(func, param1, param2)
return decorator
更新:缺少“纯类”装饰器形式
现在,请注意“混合”方法采用“两全其美”的方法,试图保持最短且更易读的代码。一个完全用类定义的“装饰器工厂”要么需要两个类,要么需要一个“模式”属性来知道它是被调用来注册装饰函数还是实际调用最终函数:
class MyDecorator(object):
"""Decorator example defined entirely as class."""
def __init__(self, p1, p2):
self.p1 = p1
...
self.mode = "decorating"
def __call__(self, *args, **kw):
if self.mode == "decorating":
self.func = args[0]
self.mode = "calling"
return self
# code to run prior to function call
result = self.func(*args, **kw)
# code to run after function call
return result
@MyDecorator(p1, ...)
def myfunc():
...
最后是一个定义了两个类的纯“白领”装饰器——也许让事情更加分离,但将冗余增加到不能说它更易于维护的程度:
class Stage2Decorator(object):
def __init__(self, func, p1, p2, ...):
self.func = func
self.p1 = p1
...
def __call__(self, *args, **kw):
# code to run prior to function call
...
result = self.func(*args, **kw)
# code to run after function call
...
return result
class Stage1Decorator(object):
"""Decorator example defined as two classes.
No "hacks" on the object model, most bureacratic.
"""
def __init__(self, p1, p2):
self.p1 = p1
...
self.mode = "decorating"
def __call__(self, func):
return Stage2Decorator(func, self.p1, self.p2, ...)
@Stage1Decorator(p1, p2, ...)
def myfunc():
...
2018 年更新
几年前我写了上面的文字。我最近想出了一个我更喜欢的模式,因为它创建了“更扁平”的代码。
基本思想是使用函数,但partial
如果在用作装饰器之前使用参数调用它,则返回自身的对象:
from functools import wraps, partial
def decorator(func=None, parameter1=None, parameter2=None, ...):
if not func:
# The only drawback is that for functions there is no thing
# like "self" - we have to rely on the decorator
# function name on the module namespace
return partial(decorator, parameter1=parameter1, parameter2=parameter2)
@wraps(func)
def wrapper(*args, **kwargs):
# Decorator code- parameter1, etc... can be used
# freely here
return func(*args, **kwargs)
return wrapper
就是这样 - 使用这种模式编写的装饰器可以立即装饰一个函数,而无需首先“调用”:
@decorator
def my_func():
pass
或自定义参数:
@decorator(parameter1="example.com", ...):
def my_func():
pass
2019 - 使用 Python 3.8 和仅位置参数,最后一种模式将变得更好,因为func
参数可以声明为仅位置参数,并且需要命名参数;
def decorator(func=None, *, parameter1=None, parameter2=None, ...):