如何编写这种类型的Python装饰器?
我想写一个装饰器,用来限制一个函数可以执行的次数,类似下面这样的语法:
@max_execs(5)
def my_method(*a,**k):
# do something here
pass
我觉得可以写出这种类型的装饰器,但我不知道怎么做。我想知道这个装饰器的第一个参数不会是一个函数,对吧?我希望能看到一个“普通装饰器”的实现,而不是一个带有call方法的类。
这样做的原因是为了学习如何写装饰器。请解释一下语法,以及这个装饰器是怎么工作的。
6 个回答
有两种方法可以实现这个功能。面向对象的方法是创建一个类:
class max_execs:
def __init__(self, max_executions):
self.max_executions = max_executions
self.executions = 0
def __call__(self, func):
@wraps(func)
def maybe(*args, **kwargs):
if self.executions < self.max_executions:
self.executions += 1
return func(*args, **kwargs)
else:
print "fail"
return maybe
想了解 wraps
的解释,可以查看 这个问题。
我更喜欢上面这种面向对象的方法来做这个装饰器,因为这样你就有了一个私有的计数变量来跟踪执行的次数。不过,另一种方法是使用闭包,比如:
def max_execs(max_executions):
executions = [0]
def actual_decorator(func):
@wraps(func)
def maybe(*args, **kwargs):
if executions[0] < max_executions:
executions[0] += 1
return func(*args, **kwargs)
else:
print "fail"
return maybe
return actual_decorator
这个方法涉及到三个函数。max_execs
函数接受一个参数,表示允许的执行次数,并返回一个装饰器,这个装饰器会限制你只能调用那么多次。这个函数,actual_decorator
,和我们在面向对象示例中的 __call__
方法做的事情是一样的。唯一有点奇怪的是,由于我们没有类和私有变量,我们需要修改在闭包外部作用域中的 executions
变量。Python 3.0 用 nonlocal
语句支持这个功能,但在 Python 2.6 或更早的版本中,我们需要把执行次数放在一个列表里,这样才能修改它。
装饰器就是一个可以被调用的东西,它可以把一个函数变成其他的东西。在你的例子中,max_execs(5)
应该是一个可以被调用的东西,它会把一个函数变成另一个可以被调用的对象,这个对象会计算调用的次数并转发这些调用。
class helper:
def __init__(self, i, fn):
self.i = i
self.fn = fn
def __call__(self, *args, **kwargs):
if self.i > 0:
self.i = self.i - 1
return self.fn(*args, **kwargs)
class max_execs:
def __init__(self, i):
self.i = i
def __call__(self, fn):
return helper(self.i, fn)
我不明白你为什么要限制自己只用函数(而不使用类)。不过如果你真的想这样做的话……
def max_execs(n):
return lambda fn, i=n: return helper(i, fn)
这是我搞出来的。它没有使用类,但用了函数属性:
def max_execs(n=5):
def decorator(fn):
fn.max = n
fn.called = 0
def wrapped(*args, **kwargs):
fn.called += 1
if fn.called <= fn.max:
return fn(*args, **kwargs)
else:
# Replace with your own exception, or something
# else that you want to happen when the limit
# is reached
raise RuntimeError("max executions exceeded")
return wrapped
return decorator
max_execs
这个函数返回了一个叫 decorator
的函数,而 decorator
又返回了 wrapped
。decoration
在两个函数属性中存储了最大执行次数和当前执行次数,然后在 wrapped
中进行检查。
翻译: 当你像这样使用装饰器时:
@max_execs(5)
def f():
print "hi!"
你基本上是在做这样的事情:
f = max_execs(5)(f)