Implementando el patrón decorador en Python

Quiero implementar el patrón de decorador en Python, y me pregunté si hay una manera de escribir un decorador que solo implemente la función que quiere modificar, sin escribir caldera-placa para todas las funciones que se reenvían al objeto decorado. Al igual que:

class foo(object): def f1(self): print "original f1" def f2(self): print "original f2" class foo_decorator(object): def __init__(self, decoratee): self._decoratee = decoratee def f1(self): print "decorated f1" self._decoratee.f1() def f2(self): # I would like to leave that part out self._decoratee.f2() 

Me gustaría que las llamadas a foo_decorator.f2 reenvíen a decoratee.f2 automáticamente. ¿Hay alguna forma de escribir un método genérico que reenvíe todas las llamadas de función no implementadas a decoratee ?

Podrías usar __getattr__ :

 class foo(object): def f1(self): print "original f1" def f2(self): print "original f2" class foo_decorator(object): def __init__(self, decoratee): self._decoratee = decoratee def f1(self): print "decorated f1" self._decoratee.f1() def __getattr__(self, name): return getattr(self._decoratee, name) u = foo() v = foo_decorator(u) v.f1() v.f2() 

Como una adición a la respuesta de Philipp; Si necesita no solo decorar, sino también preservar el tipo de un objeto, Python le permite subclasificar una instancia en tiempo de ejecución:

 class foo(object): def f1(self): print "original f1" def f2(self): print "original f2" class foo_decorator(object): def __new__(cls, decoratee): cls = type('decorated', (foo_decorator, decoratee.__class__), decoratee.__dict__) return object.__new__(cls) def f1(self): print "decorated f1" super(foo_decorator, self).f1() u = foo() v = foo_decorator(u) v.f1() v.f2() print 'isinstance(v, foo) ==', isinstance(v, foo) 

Esto es un poco más complicado de lo estrictamente necesario para su ejemplo, donde sabe que la clase está decorada con anticipación.

Esto podría ser suficiente:

 class foo_decorator(foo): def __init__(self, decoratee): self.__dict__.update(decoratee.__dict__) def f1(self): print "decorated f1" super(foo_decorator, self).f1() 

Podría decirse que no es la mejor práctica, pero puede agregar funcionalidad a las instancias, como he hecho para ayudar a la transición de mi código de ORM de Django a SQLAlachemy, de la siguiente manera:

 def _save(self): session.add(self) session.commit() setattr(Base,'save',_save) 

El diagtwig UML en el artículo de Wikipedia vinculado es incorrecto y también lo es su código.

Si sigue el “patrón de decorador”, la clase de decorador se deriva de la clase de base decorada. (En el diagtwig UML falta una flecha de herencia desde WindowDecorator a Window).

con

 class foo_decorator(foo): 

no es necesario implementar métodos no decorados.

Por cierto: en los lenguajes de tipo fuerte, hay una razón más por la que el decorador debe derivarse de la clase decorada: de lo contrario, no se podrían encadenar decoradores.

Para complementar la respuesta de @Alec Thomas. Modifiqué su respuesta para seguir el patrón de decorador. De esta manera, no es necesario que conozcas con anticipación la clase que decoras.

 class Decorator(object): def __new__(cls, decoratee): cls = type('decorated', (cls, decoratee.__class__), decoratee.__dict__) return object.__new__(cls) 

Entonces, puedes usarlo como:

 class SpecificDecorator(Decorator): def f1(self): print "decorated f1" super(foo_decorator, self).f1() class Decorated(object): def f1(self): print "original f1" d = SpecificDecorator(Decorated()) d.f1() 

En uno de mis proyectos, también tuve que hacer una cosa en particular, es decir, que incluso el objeto subyacente debería ejecutar el método que se reimplementó en el decorador. En realidad, es bastante fácil de hacer si sabes a dónde dirigirte.

El caso de uso es:

  • Tengo un objeto X con los métodos A y B.
  • Creo una clase de decorador Y que anula a A.
  • Si hago una instancia de Y (X) y llamo a A, usará la A decorada como se espera.
  • Si B llama a A, entonces si hago una instancia de Y (X) y llamo a B en el decorador, la llamada desde dentro de B entonces va a la antigua A en el objeto original, lo cual no era deseable. Quiero que la vieja B llame a la nueva A también.

Es posible llegar a este comportamiento así:

 import inspect import six # for handling 2-3 compatibility class MyBaseDecorator(object): def __init__(self, decorated): self.decorated = decorated def __getattr__(self, attr): value = getattr(self.decorated, attr) if inspect.ismethod(value): function = six.get_method_function(value) value = function.__get__(self, type(self)) return value class SomeObject(object): def a(self): pass def b(self): pass class MyDecorator(MyBaseDecorator): def a(self): pass decorated = MyDecorator(SomeObject()) 

Es posible que esto no funcione de manera inmediata, ya que escribí todo lo demás aparte del método getattr desde la parte superior de mi cabeza.

El código busca el atributo solicitado en el objeto decorado, y si es un método (no funciona para las propiedades ahora, pero el cambio para respaldarlas no debería ser demasiado difícil), el código extrae la función real de la El método y el uso de la interfaz del descriptor de invocación “vuelve a vincular” la función como un método, pero en el decorador. Luego se devuelve y lo más probable es que se ejecute.

El efecto de esto es que si b alguna vez llama a a objeto original, entonces cuando el objeto está decorado y el decorador recibe alguna llamada del método, el decorador se asegura de que todos los métodos a los que se accede estén vinculados al decorador, por lo tanto buscar cosas utilizando el decorador y no el objeto original, por lo tanto, los métodos especificados en el decorador tienen prioridad.

PD: Sí, sé que se parece bastante a la herencia, pero esto se hace en el sentido de composición de varios objetos.