我想编写一个装饰器来限制函数执行的次数,具体方法如下:
@max_execs(5)
def my_method(*a,**k):
# do something here
pass
我认为可以编写这种类型的装饰器,但我不知道如何。我认为一个函数不会是这个装饰者的第一个参数,对吗?我想要一个“普通装饰”实现,而不是一些带 调用 方法的类。
这样做的原因是要了解它们的编写方式。请解释语法,以及该装饰器的工作原理。
答案 0 :(得分:12)
这是我掀起的。它不使用类,但它确实使用函数属性:
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
的函数,该函数又返回wrapped
。 decoration
将max execs和当前exec数存储在两个函数属性中,然后在wrapped
中进行检查。
翻译:使用这样的装饰器:
@max_execs(5)
def f():
print "hi!"
你基本上是这样做的:
f = max_execs(5)(f)
答案 1 :(得分:4)
Decorator只是一个可调用函数,可以将函数转换为其他函数。在您的情况下,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)
答案 2 :(得分:3)
有两种方法可以做到这一点。面向对象的方法是创建一个类:
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
的解释,请参阅this question。
我更喜欢上述OOP方法用于这种装饰器,因为你基本上有一个私有计数变量来跟踪执行次数。但是,另一种方法是使用闭包,例如
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
与OOP示例中的__call__
方法完全相同。唯一的奇怪之处在于,由于我们没有带私有变量的类,我们需要改变闭包外部范围内的executions
变量。 Python 3.0使用nonlocal
语句支持这一点,但在Python 2.6或更早版本中,我们需要将执行计数包装在一个列表中,以便可以对其进行变异。
答案 3 :(得分:2)
如果不依赖于类中的状态,则必须在函数本身中保存状态(count):
def max_execs(count):
def new_meth(meth):
meth.count = count
def new(*a,**k):
meth.count -= 1
print meth.count
if meth.count>=0:
return meth(*a,**k)
return new
return new_meth
@max_execs(5)
def f():
print "invoked"
[f() for _ in range(10)]
它给出了:
5
invoked
4
invoked
3
invoked
2
invoked
1
invoked
0
-1
-2
-3
-4
答案 4 :(得分:1)
此方法不会修改函数内部,而是将其包装到可调用对象中。
与使用修补函数相比,使用类会使执行速度降低约20%!
def max_execs(n=1):
class limit_wrapper:
def __init__(self, fn, max):
self.calls_left = max
self.fn = fn
def __call__(self,*a,**kw):
if self.calls_left > 0:
self.calls_left -= 1
return self.fn(*a,**kw)
raise Exception("max num of calls is %d" % self.i)
def decorator(fn):
return limit_wrapper(fn,n)
return decorator
@max_execs(2)
def fun():
print "called"
答案 5 :(得分:0)
我知道你说你不想上课,但不幸的是,这是我能想到如何做到这一点的唯一方法。
class mymethodwrapper:
def __init__(self):
self.maxcalls = 0
def mymethod(self):
self.maxcalls += 1
if self.maxcalls > 5:
return
#rest of your code
print "Code fired!"
像这样点燃它
a = mymethodwrapper
for x in range(1000):
a.mymethod()
输出结果为:
>>> Code fired!
>>> Code fired!
>>> Code fired!
>>> Code fired!
>>> Code fired!