2010-06-25 13 views
21

Quiero implementar el decorator pattern en Python, y me pregunté si hay una manera de escribir un decorador que simplemente implemente la función que quiere modificar, sin escribir placa de caldera para todas las funciones que solo se envían al objeto decorado. De este modo:Implementando el patrón de decorador en Python

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 tener llamadas a foo_decorator.f2 reenviados a decoratee.f2 automáticamente. ¿Hay alguna manera de escribir un método genérico que reenvía todas las llamadas a función no implementadas al decoratee?

+1

¿Puedes dar un ejemplo en el que simplemente no funcionaría la creación de subclases? También puede crear subclase dinámicamente: este patrón parece una solución alternativa para los idiomas que no pueden hacer eso o que no son compatibles con el patrimonio múltiple. –

+0

Quiero decorar objetos en tiempo de ejecución. Quiero aplicar diferentes decoradores a un objeto y ser capaz de eliminarlos de nuevo. La creación de subclases no puede cambiar una instancia después de que se haya creado, ¿o sí? –

+0

Si tiene la clase 'A' y cambia' A', es decir, agrega un nuevo método, 'A.foo = lambda self: self' esto se reflejará en todas las instancias de A ... porque * todo * se determina en tiempo de ejecución. Una gran manera de producir código absolutamente inmanejable. –

Respuesta

30

usted podría utilizar __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() 
+1

Esta es una gran idea. Sin embargo, debe tenerse en cuenta que si utiliza Clases base abstractas (es decir, si importa abc y utiliza __metaclass__ = abc.ABCMeta para definir métodos y propiedades abstractos), no funcionará. – abergou

+0

Esto no funciona para funciones como __str__. ¿Lo que está sucediendo allí? –

+0

@JohnKitchin: la redirección no funciona para los métodos de subrayado doble, porque estos deben (por motivos de rendimiento) definirse en la clase, no en la instancia. [(más información)] (https://docs.python.org/3/reference/datamodel.html#special-lookup) Creo que la única solución es definir explícitamente y redirigir métodos como '__str__'. –

2

No es sin duda la mejor práctica, pero se puede agregar funcionalidad a instancias, como lo he hecho para ayudar a la transición mi código de ORM de Django para SQLAlachemy, de la siguiente manera:

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

Consideré esto también, pero me parece muy mal. Tal vez eso es porque vengo de C++? –

+2

Es justo sentirse mal al respecto. Podría considerarse parche de mono. Sin embargo, funciona bien con poco código, es un mundo muy diferente con más posibilidades de C cuando todo es dinámico y por referencia. – andyortlieb

+0

Me gusta. Para este ejemplo, cualquier otra cosa simplemente introduciría más complejidad IMO. – Chomeh

1

El diagrama de UML en el artículo vinculado de Wikipedia es incorrecto, al igual que su código.

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

con

class foo_decorator(foo): 

que no es necesario para poner en práctica los métodos sin decorar.

BTW: En los lenguajes de caracteres fuertes hay una razón más por la que el decorador debe derivarse de la clase decorada: De lo contrario, no sería posible encadenar decoradores.

+0

El diagrama UML muestra tanto la herencia como la agregación (dos partes esenciales para el patrón del decorador) de la clase base.Usted hereda de la clase base, por lo que se parece al original, y mantiene una referencia a una instancia de la clase base donde puede aislar el acceso a ella. El artículo de wikipedia dice esto en los pasos 1 y 2: "(1) Subclase del Componente original, (2) agregue un puntero de Componente como un campo". ¡Así que la pregunta original es sobre el tipado de pato de Python, y ni sobre el decorador ni sobre los decoradores de Python! – maxpolk

8

Como un apéndice a la respuesta de Philipp; si es necesario, no sólo para decorar, pero preservar la tipo de un objeto, Python le permite subclase 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 saber que la clase está decorada por adelantado.

Este fuerza 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() 
0

Para complementar @Alec Thomas respuesta. Modifiqué su respuesta para seguir el patrón del decorador. De esta forma, no es necesario que conozcas la clase que estás decorando por adelantado.

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

A continuación, puede utilizarlo 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() 
0

En uno de mis proyectos, yo también tenía que hacer una cosa en particular, es que incluso el objeto subyacente en realidad debería ejecutar el método que fue reimplementado en el decorador. En realidad, es bastante fácil de hacer si sabes 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.
  • Si instancia Y (X) y llamo A, utilizará la A decorada como se esperaba.
  • Si B llama a A, entonces si hago una instancia de Y (X) y llamo B en el decorador, la llamada desde dentro de B va a la antigua A en el objeto original que era indeseable. Quiero que el viejo B llame al nuevo A también.

Es posible llegar a este comportamiento como éste:

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

Esto no puede trabajar fuera de la caja mientras escribía todo lo demás aparte del método getattr desde la parte superior de la cabeza.

El código busca el atributo solicitado en el objeto decorado, y si se trata de un método (no funciona para las propiedades ahora, pero el cambio de apoyarlos no debe ser demasiado difícil), el código a continuación, tira de la real funcionar fuera del método y usar la invocación de la interfaz del descriptor "revuelve" 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 llama al a en el objeto original, cuando el objeto está decorado y hay una llamada al método proveniente del decorador, el decorador se asegura de que todos los métodos accedidos estén vinculados al decorador en su lugar, 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.

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

Cuestiones relacionadas