2010-09-23 16 views
17

Los decoradores de Python son divertidos, pero parece que se han pegado a la pared debido a la forma en que se transmiten los argumentos a los decoradores. Aquí tengo un decorador definido como parte de una clase base (el decorador tendrá acceso a los miembros de la clase, por lo tanto, requerirá el parámetro automático).Los decoradores de Python que forman parte de una clase base no se pueden usar para decorar las funciones de los miembros en las clases heredadas

class SubSystem(object): 
    def UpdateGUI(self, fun): #function decorator 
     def wrapper(*args): 
      self.updateGUIField(*args) 
      return fun(*args) 
     return wrapper 

    def updateGUIField(self, name, value): 
     if name in self.gui: 
      if type(self.gui[name]) == System.Windows.Controls.CheckBox: 
       self.gui[name].IsChecked = value #update checkbox on ui 
      elif type(self.gui[name]) == System.Windows.Controls.Slider: 
       self.gui[name].Value = value # update slider on ui 

     ... 

He omitido el resto de la implementación. Ahora esta clase es una clase base para varios subsistemas que heredarán de ella; algunas de las clases heredadas necesitarán usar el decorador UpdateGUI.

class DO(SubSystem): 
    def getport(self, port): 
     """Returns the value of Digital Output port "port".""" 
     pass 

    @SubSystem.UpdateGUI 
    def setport(self, port, value): 
     """Sets the value of Digital Output port "port".""" 
     pass 

Una vez más me han omitido las implementaciones de funciones, ya que no son relevantes.

En resumen, el problema es que mientras pueda acceder al decorador definidos en la clase base de la clase heredada por specifiying como SubSystem.UpdateGUI, que en última instancia conseguir este TypeError cuando se trata de usarlo:

unbound method UpdateGUI() must be called with SubSystem instance as first argument (got function instance instead)

Esto se debe a que no tengo una forma inmediatamente identificable de pasar el parámetro self al decorador.

¿Hay alguna manera de hacerlo? ¿O alcancé los límites de la implementación del decorador actual en Python?

Respuesta

17

Necesita hacer UpdateGUI a @classmethod, y haga que su wrapper tenga en cuenta self. Un ejemplo de trabajo:

class X(object): 
    @classmethod 
    def foo(cls, fun): 
     def wrapper(self, *args, **kwargs): 
      self.write(*args, **kwargs) 
      return fun(self, *args, **kwargs) 
     return wrapper 

    def write(self, *args, **kwargs): 
     print(args, kwargs) 

class Y(X): 
    @X.foo 
    def bar(self, x): 
     print("x:", x) 

Y().bar(3) 
# prints: 
# (3,) {} 
# x: 3 
+0

Esto funcionó! Nunca se hubiera pensado en convertir al decorador en un método de clase. – Aphex

+1

Para referencia futura, esta fue literalmente una corrección de línea al agregar @classmethod antes de def UpdateGUI (self, fun). – Aphex

+0

@Aphex. debe reemplazar el uso de 'self' con el uso de' cls' como KennyTM mostró. Esto hará que su código sea mucho más fácil de leer. – aaronasterling

2

Debe usar una instancia de SubSystem para decorar o usar classmethod como sugiere kenny.

subsys = SubSystem() 
class DO(SubSystem): 
    def getport(self, port): 
     """Returns the value of Digital Output port "port".""" 
     pass 

    @subsys.UpdateGUI 
    def setport(self, port, value): 
     """Sets the value of Digital Output port "port".""" 
     pass 

Usted decide qué hacer al decidir si desea que todos los instancias de las subclases de compartir la misma interfaz gráfica de usuario o si desea ser capaz de dejar que los distintos tienen interfaces distintas.

Si comparten la misma interfaz GUI, utilice un método de clase y haga que todo lo que el decorador acceda a una instancia de clase.

Si pueden tener interfaces distintas, tiene que decidir si quiere representar la distinción con la herencia (en cuyo caso también se usaría classmethod y llamar al decorador en las subclases de SubSystem) o si es mejor representado como instancias distintas. En ese caso, haga una instancia para cada interfaz y llame al decorador en esa instancia.

+0

Podría funcionar, pero crear una instancia única de la clase base solo para llamar al decorador es una mala idea. Entre otras cosas, 'self 'referiría diferentes objetos dentro del decorador y los otros métodos. Luego debe instanciar un objeto que quizás no desee. Más bien, elija otras opciones sugeridas en otras respuestas, como la definición de método estático o classmethod. – vokimon

3

Podría ser más fácil simplemente tirar del decorador de la clase SubSytem: (Tenga en cuenta que estoy suponiendo que el self que llama setport es el mismo self que desea utilizar para llamar updateGUIField)

def UpdateGUI(fun): #function decorator 
    def wrapper(self,*args): 
     self.updateGUIField(*args) 
     return fun(self,*args) 
    return wrapper 

class SubSystem(object): 
    def updateGUIField(self, name, value): 
     # if name in self.gui: 
     #  if type(self.gui[name]) == System.Windows.Controls.CheckBox: 
     #   self.gui[name].IsChecked = value #update checkbox on ui 
     #  elif type(self.gui[name]) == System.Windows.Controls.Slider: 
     #   self.gui[name].Value = value # update slider on ui 
     print(name,value) 

class DO(SubSystem): 
    @UpdateGUI 
    def setport(self, port, value): 
     """Sets the value of Digital Output port "port".""" 
     pass 

do=DO() 
do.setport('p','v') 
# ('p', 'v') 
+0

Claro, tengo otros decoradores en mi proyecto que simplemente se sientan solos y los importo según sea necesario. Sin embargo, dado que este decorador utiliza un miembro específico de la instancia (self.updateGUIField) tiene que ser parte de la clase. – Aphex

+1

@Aphex: ¿Has probado mi código? Creo que funciona bien, sin que el decorador forme parte de la clase. – unutbu

+0

Esto probablemente rompa el acoplamiento que @Aphex quiere. 'UpdateGUI' no tiene sentido sin ser llamado en el contexto de' SubSystem' (o subclases de 'SubSystem') por lo que debe ser un método estático o de clase. – cowbert

3

has tipo de respuesta es la pregunta en pedir que:. qué argumento se puede esperar a llegar lo más self si se llama a SubSystem.UpdateGUI? No hay una instancia obvia que deba pasarse al decorador.

Hay varias cosas que podría hacer para evitar esto. ¿Tal vez ya tiene un subSystem que ha instanciado en otro lugar? Posteriormente, se podría utilizar su decorador:

subSystem = SubSystem() 
subSystem.UpdateGUI(...) 

Pero tal vez usted no necesita la instancia en el primer lugar, sólo la clase SubSystem? En ese caso, utilice el decorador classmethod Python para decirle que esta función debe recibir su clase como primer argumento en lugar de una instancia:

@classmethod 
def UpdateGUI(cls,...): 
    ... 

Por último, tal vez usted no necesitan tener acceso a cualquiera de la instancia o la clase ! En ese caso, utilice staticmethod:

@staticmethod 
def UpdateGUI(...): 
    ... 

Ah, por cierto, la convención de Python es reservar nombres CamelCase para las clases y utilizar MixedCase o nombres under_scored para los métodos de esa clase.

+0

@ Aaron: cierto, gracias. – katrielalex

+0

@ self.UpdateGUI no funciona, porque self no tiene ningún significado en el alcance de la definición de clase. Es por eso que usé el nombre de la clase base. – Aphex

Cuestiones relacionadas