2009-07-09 102 views
6

我想写一个装饰将限制次数可以执行的功能,以及语法如下一些:这些类型的python装饰器是如何编写的?


@max_execs(5) 
def my_method(*a,**k): 
    # do something here 
    pass 

我认为这是可能写这种类型的装饰,但我不不知道如何。我认为一个函数不会是这个装饰器的第一个参数,对吧?我想要一个“普通的装饰器”的实现,而不是一些有调用方法的类。

原因是要了解它们是如何书写的。请解释语法,以及装饰器的工作原理。

回答

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,这反过来又返回wrappeddecoration在两个函数属性中存储最大执行数和当前exec数,然后在wrapped中进行检查。

翻译:当使用像这样的装饰:

@max_execs(5) 
def f(): 
    print "hi!" 

你基本上做这样的事情:

f = max_execs(5)(f) 
+0

一样,python代码是如何“翻译”的?例如,如果我的方法被称为blabla,并且我应用了max_execs属性,那么Python将如何看待它? blabla = max_execs(5)(blabla)? – Geo 2009-07-09 20:35:05

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! 
+0

这不能用于任何方法,并使用可调用的对象。我想要一个普通的装饰者。 – Geo 2009-07-09 20:28:36

4

装饰仅仅是一种把函数转换成别的东西调用。在你的情况下,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) 
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或更早版本中,我们需要将我们的执行计数包含在列表中,以便它可以进行变异。

+1

如果例如具有@logged属性的方法表示method = logged(method),那么具有@max_execs(5)“方法”的方法是如何“翻译”的? – Geo 2009-07-09 20:31:05

+0

这和应用这个装饰器时说的max_execs(5)(f) – 2009-07-09 20:37:41

2

不依靠这样的状态,一类,你必须保存功能本身的状态(计数):

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 
1

此方法不会修改函数内部,而是将其包装到可调用对象中。

与使用修补功能相比,使用class减慢了执行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"