2009-07-09 19 views
6

me gustaría escribir un decorador que limitaría el número de veces que una función puede ser ejecutada, algo a lo largo de la siguiente sintaxis:¿Cómo se escribe este tipo de decoradores de pitón?


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

Creo que es posible escribir este tipo de decorador, pero Don no sé cómo Creo que una función no será el primer argumento de este decorador, ¿verdad? Me gustaría una implementación de "decorador simple", no una clase con un llamada método.

El motivo es aprender cómo están escritos. Por favor, explique la sintaxis y cómo funciona el decorador.

Respuesta

12

Esto es lo que aumenté. No utiliza una clase, pero utiliza los atributos de la función:

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 devuelve un llamado funcionado decorator, que a su vez devuelve wrapped.decoration almacena los ejecutivos máximos y el número actual de ejecutivos en dos atributos de función, que luego se marcan en wrapped.

Traducción: Cuando se utiliza el decorador de la siguiente manera:

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

Básicamente, se está haciendo algo como esto:

f = max_execs(5)(f) 
+0

al aplicar este decorador, ¿cómo se "traduce" el código por python? Por ejemplo, si mi método se llama blabla, y aplico el atributo max_execs, ¿cómo lo verá Python? blabla = max_execs (5) (blabla)? – Geo

+1

Actualicé la respuesta para incluir la traducción, pero tiene la idea correcta. – mipadi

0

Sé que dijiste que no querías una clase, pero desafortunadamente esa es la única forma en que puedo pensar cómo hacerlo de la nada.

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!" 

fuego que de esta manera

a = mymethodwrapper 
for x in range(1000): 
    a.mymethod() 

La salida sería:

>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
>>> Code fired! 
+0

esto no se puede utilizar con ningún método, y utiliza un objeto invocable. Me gustaría un decorador regular. – Geo

4

decorador es simplemente un exigible que transforma una función en otra cosa. En su caso, max_execs(5) debe ser un invocable que transforma una función en otro objeto invocable que contará y reenviará las llamadas.

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) 

No veo por qué querría limitarse a una función (y no a una clase). Pero si realmente desea ...

def max_execs(n): 
    return lambda fn, i=n: return helper(i, fn) 
3

Existen dos formas de hacerlo. La forma orientada a objetos es hacer una clase:

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 

Ver this question para una explicación de wraps.

Prefiero el enfoque OOP anterior para este tipo de decorador, ya que básicamente tiene una variable de conteo privada que rastrea el número de ejecuciones. Sin embargo, el otro enfoque es utilizar un cierre, como

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 

Esto implicaba tres funciones. A la función max_execs se le asigna un parámetro para el número de ejecuciones y devuelve un decorador que lo restringirá a esa cantidad de llamadas. Esa función, actual_decorator, hace lo mismo que nuestro método __call__ en el ejemplo OOP. La única rareza es que, dado que no tenemos una clase con variables privadas, tenemos que mutar la variable executions que se encuentra en el ámbito externo de nuestro cierre. Python 3.0 admite esto con la instrucción nonlocal, pero en Python 2.6 o anterior, necesitamos ajustar el número de ejecuciones en una lista para que pueda mutarse.

+1

si, por ejemplo, un método con el atributo @logged significa method = logged (método), ¿cómo se "traduce" un método con @max_execs (5)? – Geo

+0

Es lo mismo que decir max_execs (5) (f) –

2

Sin depender de un estado en una clase, se tiene que guardar el estado (recuento) en la función en sí:

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)] 

Da:

5 
invoked 
4 
invoked 
3 
invoked 
2 
invoked 
1 
invoked 
0 
-1 
-2 
-3 
-4 
+0

Vaya, mipadi fue más rápido con una solución similar. –

1

Este método no modifica las funciones internas, sino que las envuelve en un objeto invocable.

¡El uso de la clase ralentiza la ejecución en ~ 20% frente a la utilización de la función de parcheado!

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" 
Cuestiones relacionadas