2011-07-13 23 views
27

En Python hay dos formas de declarar decoradores:decoradores de clase vs decoradores de función

Clase basada

class mydecorator(object): 
    def __init__(self, f): 
     self.f = f 

    def __call__(self, *k, **kw): 
     # before f actions 
     self.f(*k, **kw) 
     # after f actions 

función basada

def mydecorator(f): 
    def decorator(*k, **kw): 
     # before f actions 
     f(*k, **kw) 
     # after f actions 

    return decorator   

¿Hay alguna diferencia entre estos declaraciones? ¿En qué casos debería usarse cada uno de ellos?

+1

¡Con las clases, puede hacer uso del protocolo descriptor! – phant0m

+0

@ phant0m: En el lado negativo, con las clases que * tiene * para usar el protocolo de descriptores (de lo contrario, su decorador no funcionará exactamente en los métodos). –

+0

@Rosh: Hmm sí, tienes un punto muy fuerte aquí :) – phant0m

Respuesta

18

Si desea mantener el estado en el decorador, debe utilizar una clase.

Por ejemplo, esto no funciona

def mydecorator(f): 
    x = 0 
    def decorator(): 
     x += 1 # x is a nonlocal name and cant be modified 
     return f(x) 
    return decorator 

Hay muchas soluciones para esto, pero la forma más sencilla es utilizar una clase

class mydecorator(object): 
    def __init__(self, f): 
     self.f = f 
     self.x = 0 

    def __call__(self, *k, **kw): 
     self.x += 1 
     return f(self.x) 
+2

Creo que la forma más simple sería decir 'no local x; x + = 1'. ¿O eso no funciona en las versiones de Python por debajo de 3? - Sí, parece que 'nonlocal' se introdujo en Python 3. – JAB

+0

Más información sobre las limitaciones de los decoradores de métodos está disponible en http://stackoverflow.com/questions/6676015/class-decorators-vs-function-decorators/6677524# 6677524 respuesta. – Yossi

+1

Un decorador con estado que es utilizado por más de un módulo hace que cualquier uso de él dependa del * orden en el que otros módulos que lo usan fueron importados ** en otro lugar del programa, incluso en módulos completamente independientes **! * Así que mi primer La regla general sería, si desea mantener el estado en un decorador, "deténgase y piense si este es el mejor enfoque".A veces, por supuesto, será, en cuyo caso sí, debería usar un decorador de clase. – Ben

6

De hecho, no hay "dos formas". Solo hay una forma (definir un objeto llamable) o tantas formas como hay en python para hacer un objeto invocable (podría ser un método de otro objeto, un resultado de expresión lambda, un objeto 'parcial', cualquier cosa que sea invocable).

La definición de la función es la forma más fácil de crear un objeto invocable y, como la más simple, es probablemente la mejor en la mayoría de los casos. Usar una clase te da más posibilidades de codificar limpiamente casos más complicados (incluso en los casos más simples se ve bastante elegante), pero no es tan obvio lo que hace.

+0

+1 profunda respuesta –

2

No, hay (más de) dos formas de hacer objetos invocables. Uno es para def una función, que obviamente es invocable. Otra es definir un método __call__ en una clase, lo que hará que las instancias puedan invocarse. Y las clases mismas son objetos invocables.

Un decorador no es más que un objeto invocable, que es previsto para aceptar una función como su único argumento y devolver algo invocable. La siguiente sintaxis:

@decorate 
def some_function(...): 
    ... 

es sólo una manera un poco más bonitas de la escritura:

def some_function(...): 
    ... 

some_function = decorate(some_function) 

El ejemplo basado en clases que da no es una función que toma una función y devuelve una función, que es el decorador de vainilla estándar de pantano, es una clase que se inicializa con una función cuyas instancias son invocables. Para mí, esto es un poco raro si en realidad no lo estás usando como clase (¿tiene otros métodos? ¿Su estado cambia? ¿Haces varias instancias de él que tengan un comportamiento común encapsulado por la clase?). Pero el uso normal de su función decorada no notará la diferencia (a menos que sea un decorador particularmente invasivo), así que haga lo que le parezca más natural.

1

¡Vamos a probarlo!

test_class = """ 
class mydecorator_class(object): 
    def __init__(self, f): 
     self.f = f 

    def __call__(self, *k, **kw): 
     # before f actions 
     print 'hi class' 
     self.f(*k, **kw) 
     print 'goodbye class' 
     # after f actions 

@mydecorator_class 
def cls(): 
    print 'class' 

cls() 
""" 

test_deco = """ 
def mydecorator_func(f): 
    def decorator(*k, **kw): 
     # before f actions 
     print 'hi function' 
     f(*k, **kw) 
     print 'goodbye function' 
     # after f actions 
    return decorator 

@mydecorator_func 
def fun(): 
    print 'func' 

fun() 
""" 

if __name__ == "__main__": 
    import timeit 
    r = timeit.Timer(test_class).timeit(1000) 
    r2 = timeit.Timer(test_deco).timeit(1000) 
    print r, r2 

resultados que tengo como este: 0,0499339103699 0,0824959278107

Esto significa que es deco clase 2 veces más rápido?

+2

Interesante, pero supongo que es específico de la implementación. Con otras implementaciones de Python (Jython, PyPy) o incluso diferentes versiones de CPython, probablemente obtendrás resultados bastante diferentes. Además, esa es probablemente una diferencia en el tiempo que se ejecuta 'mydecorator_func' y el código de definición de clase. Este es solo un tiempo de inicio para la aplicación y generalmente no importa mucho. La velocidad de llamada a la función decorada probablemente será la misma en ambos casos. –

16

Cuando se está creando un exigible volver otra exigible , el enfoque de función es más fácil y más barato. Hay dos diferencias principales:

  1. enfoque La función funciona automáticamente con los métodos, mientras que si usted está utilizando su enfoque de clase, usted tiene que leer los descriptores y definir un método __get__.
  2. El enfoque de clase facilita el mantenimiento del estado. Podría usar un cierre, especialmente en Python 3, pero generalmente se prefiere un enfoque de clase para mantener el estado.

Además, el enfoque de la función le permite volver a la función original de , después de modificar o almacenarlo.

Sin embargo, un decorador puede devolver algo distinto de un llamativo o algo más que un llamador. Con una clase, puede:

  1. Agregar métodos y propiedades al objeto llamable decorado, o implementar operaciones en ellos (uh-oh).
  2. Crear descriptores que actúan de una manera especial cuando se coloca en clases (por ejemplo classmethod, property)
  3. utilizar la herencia para implementar decoradores similares pero diferentes.

Si tiene alguna duda, pregúntese: ¿Desea que su decorador devuelva una función que funciona exactamente como una función? Use una función que devuelva una función. ¿Desea que su decorador le devuelva un objeto personalizado que hace algo más o algo diferente de lo que hace una función? Crea una clase y úsala como decorador.

+0

+1 ¡Escandaloso que nadie más haya notado el problema del método! – delnan

+1

+1 Respuesta muy completa. Tarde a la fiesta, pero esta probablemente debería ser la respuesta aceptada. –

+0

"El enfoque de función funciona automáticamente con métodos" Esa es una distinción significativa. ¡Gracias! –

Cuestiones relacionadas