2010-08-12 9 views
35

Realmente no necesito hacer esto, pero me preguntaba, ¿hay alguna forma de vincular a un decorador a todas las funciones dentro de una clase genéricamente, en lugar de indicarlo explícitamente para cada función.Adjuntar un decorador a todas las funciones dentro de una clase

Supongo que se convierte en una especie de aspecto, en lugar de un decorador y se siente un poco extraño, pero estaba pensando en algo así como el tiempo o la autenticación que sería bastante limpio.

Respuesta

20

La manera más limpia de hacer esto, o de hacer otras modificaciones en una definición de clase, es definir una metaclase.

Alternativamente, solo se aplica su decorador al final de la definición de clase:

class Something: 
    def foo(self): pass 

for name, fn in inspect.getmembers(Something): 
    if isinstance(fn, types.UnboundMethodType): 
     setattr(Something, name, decorator(fn)) 

Para reemplazar Python 3 types.UnboundMethodType con types.FunctionType.

En la práctica, por supuesto, querrá aplicar su decorador de forma más selectiva, y en cuanto quiera decorar todos los métodos menos uno, descubrirá que es más fácil y flexible simplemente usar la sintaxis del decorador en el manera tradicional.

+0

Estoy totalmente de acuerdo, también estoy mucho más cómodo con la decoración explícita de cada función en lugar de un decorador mágico implícito. Era curioso, es todo. – dochead

-1

Puede anular el método __getattr__. En realidad, no está adjuntando un decorador, pero te permite devolver un método decorado. Usted probablemente querrá hacer algo como esto:

class Eggs(object): 
    def __getattr__(self, attr): 
     return decorate(getattr(self, `_` + attr)) 

Hay un poco de recursividad fea ocultando allí que usted quiere proteger en contra, pero eso es un comienzo.

+0

No quiere decir '__getattribute__'? –

+0

@JaceBrowning: No, no lo creo. Si Eggs.attr se llama realmente Eggs._attr, esto funcionará. Si desea anular el acceso a Eggs.attr, que es un atributo real, entonces tal vez. Ahí es donde entran las cosas recursivas difíciles. – nmichaels

+0

Entiendo lo que estás haciendo ahora: todos tus métodos sin decorar comienzan con '_'. Podrías evitar esto usando '__getattribute__', pero tienes razón, hay alguna recursión complicada para evitar. –

21

Cada vez que piense en cambiar la definición de clase, puede usar el decorador de clase o la metaclase. p.ej. utilizando metaclase

import types 

class DecoMeta(type): 
    def __new__(cls, name, bases, attrs): 

     for attr_name, attr_value in attrs.iteritems(): 
     if isinstance(attr_value, types.FunctionType): 
      attrs[attr_name] = cls.deco(attr_value) 

     return super(DecoMeta, cls).__new__(cls, name, bases, attrs) 

    @classmethod 
    def deco(cls, func): 
     def wrapper(*args, **kwargs): 
     print "before",func.func_name 
     result = func(*args, **kwargs) 
     print "after",func.func_name 
     return result 
     return wrapper 

class MyKlass(object): 
    __metaclass__ = DecoMeta 

    def func1(self): 
     pass 

MyKlass().func1() 

Salida:

before func1 
after func1 

Nota: no va a decorar métodoestático y classmethod

+1

Excelente información. Utilicé esta técnica para crear métodos de prueba en [Gold/archivo aprobado probando todos los métodos en una clase de prueba contra cada archivo en un directorio a través de la metaprogramación de Metaclass en Python] (https://gist.github.com/patkujawa-wf/1f569d245bbfc73f83a3) . – Pat

+0

Realmente no entiendo la diferencia entre '__new__' y' __init__' cuando se trata de metaclases. Ambos parecen trabajar para este problema, aunque obtienen diferentes argumentos. – Pat

+0

@Pat, es similar a '__new__' y' __init__' para una clase normal, '__new__' se llama para construir el objeto (clase en este caso),' __init__' se invoca para inicializar ese objeto (clase en este caso) , por lo que en la mayoría de los casos puede no ser importante a menos que tenga el requisito de hacer algo antes o después de la creación de clase/obj. –

1

Por supuesto que las metaclases son la forma más Pythonic ir cuando se quiere modificar la forma ese pitón crea los objetos. Lo cual se puede hacer anulando el método __new__ de su clase. Sin embargo, hay algunos puntos en torno a este problema (especialmente para 3.X Python) que me gustaría mencionar:

  1. types.FunctionType no protege los métodos especiales de ser decorado, ya que son los tipos de función. Como una forma más general, puede decorar los objetos cuyos nombres no se inician con doble guión bajo (__). Otra ventaja de este método es que también cubre los objetos que existen en espacio de nombres y comienza con __ pero no se funciona como __qualname__, __module__, etc.
  2. El argumento namespace en la cabecera __new__ 's no contiene atributos de clase dentro del __init__. La razón es que el __new__ se ejecuta antes del __init__ (inicialización).

  3. No es necesario usar un classmethod como decorador, ya que la mayoría de las veces importa el decorador desde otro módulo.

  4. Si la clase es contener un elemento mundial (fuera del lado de la __init__) por negarse a ser decorada junto comprobar si el nombre no se ha iniciado con __ se puede comprobar el tipo con types.FunctionType estar seguro de que usted no está decorando un objeto sin función.

Aquí es una metacalss de ejemplo que puede utilizar:

class TheMeta(type): 
    def __new__(cls, name, bases, namespace, **kwds): 
     # if your decorator is a class method of the metaclass use 
     # `my_decorator = cls.my_decorator` in order to invoke the decorator. 
     namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()} 
     return type.__new__(cls, name, bases, namespace) 

Demostración:

def my_decorator(func): 
     def wrapper(self, arg): 
      # You can also use *args instead of (self, arg) and pass the *args 
      # to the function in following call. 
      return "the value {} gets modified!!".format(func(self, arg)) 
     return wrapper 


class TheMeta(type): 
    def __new__(cls, name, bases, namespace, **kwds): 
     # my_decorator = cls.my_decorator (if the decorator is a classmethod) 
     namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()} 
     return type.__new__(cls, name, bases, namespace) 


class MyClass(metaclass=TheMeta): 
    # a = 10 
    def __init__(self, *args, **kwargs): 
     self.item = args[0] 
     self.value = kwargs['value'] 

    def __getattr__(self, attr): 
     return "This class hasn't provide the attribute {}.".format(attr) 

    def myfunction_1(self, arg): 
     return arg ** 2 

    def myfunction_2(self, arg): 
     return arg ** 3 

myinstance = MyClass(1, 2, value=100) 
print(myinstance.myfunction_1(5)) 
print(myinstance.myfunction_2(2)) 
print(myinstance.item) 
print(myinstance.p) 

Salida:

the value 25 gets modified!! 
the value 8 gets modified!! 
1 
This class hasn't provide the attribute p. # special method is not decorated. 

Para la comprobación de la tercera i TEM de las notas antes mencionadas, puede descomentar la línea a = 10 y hacer print(myinstance.a) y ver el resultado a continuación, cambiar el diccionario por comprensión en __new__ como sigue entonces ver el resultado de nuevo:

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\ 
      else my_decorator(v) for k, v in namespace.items()} 
Cuestiones relacionadas