2010-09-23 8 views
17

I decoratori Python sono divertenti da usare, ma sembra che abbia colpito un muro a causa del modo in cui gli argomenti vengono passati ai decoratori. Qui ho un decoratore definito come parte di una classe base (il decoratore accederà ai membri della classe quindi richiederà il parametro auto).I decoratori Python che fanno parte di una classe base non possono essere usati per decorare le funzioni membro nelle classi ereditate

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 

     ... 

Ho omesso il resto dell'implementazione. Ora questa classe è una classe base per vari sottosistemi che erediteranno da esso: alcune delle classi ereditate dovranno utilizzare il decoratore 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 

Ancora una volta ho omesso le implementazioni di funzione in quanto non pertinenti.

In breve, il problema è che mentre posso accedere decoratore definito nella classe base dalla classe ereditata da specifiying come SubSystem.UpdateGUI, ho infine ottenere questo TypeError quando si tenta di usarlo:

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

Questo perché non ho un modo immediatamente identificabile per passare il parametro self al decoratore!

C'è un modo per farlo? O ho raggiunto i limiti dell'attuale implementazione del decoratore in Python?

risposta

17

È necessario effettuare UpdateGUI a @classmethod e rendere il proprio wrapper consapevole di self. Un esempio di lavoro:

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

Questo ha funzionato! Non avrei mai pensato di trasformare il decoratore in un metodo di classe. – Aphex

+1

Per riferimento futuro, questa è stata letteralmente una correzione su una riga aggiungendo @classmethod prima di def UpdateGUI (self, fun). – Aphex

+0

@Aphex. dovresti sostituire l'uso di 'self' con l'uso di' cls' come mostrato da KennyTM. Questo renderà il tuo codice molto più facile da leggere. – aaronasterling

2

È necessario utilizzare un'istanza di SubSystem per decorare o utilizzare uno classmethod come suggerisce 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 

Si decide che fare decidendo se si desidera che tutti i le istanze delle sottoclassi di condividere la stessa interfaccia GUI o se si vuole essere in grado di lasciare che quelli distinti hanno interfacce distinte.

Se tutti condividono la stessa interfaccia GUI, utilizzare un metodo di classe e creare tutto ciò che il decoratore accede a un'istanza di classe.

Se possono avere interfacce distinte, è necessario decidere se si vuole rappresentare la distinzione con l'ereditarietà (nel qual caso si dovrebbe anche usare classmethod e chiamare il decoratore sulle sottoclassi di SubSystem) o se è meglio rappresentato come istanze distinte. In tal caso, crea un'istanza per ogni interfaccia e chiama il decoratore su quell'istanza.

+0

Potrebbe funzionare ma creare un'istanza singleton della classe base solo per chiamare il decoratore è una cattiva idea. Tra l'altro, 'self' si riferirebbe a diversi oggetti all'interno del decoratore e degli altri metodi. Quindi devi istanziare un oggetto che potresti non volere. Piuttosto, scegliere altre opzioni suggerite in altre risposte, come la definizione di staticmethod o classmethod. – vokimon

3

Potrebbe essere più facile da tirare appena il decoratore fuori della classe SubSytem: (Si noti che sto supponendo che il self che chiama setport è lo stesso self che si desidera utilizzare per chiamare 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

Certo, ho altri decoratori nel mio progetto che si limitano a sedersi da soli e li impongo come necessario. Tuttavia, poiché questo decoratore utilizza un membro specifico dell'istanza (self.updateGUIField), deve essere parte della classe. – Aphex

+1

@Aphex: hai provato il mio codice? Penso che funzioni bene, senza che il decoratore faccia parte della classe. – unutbu

+0

Questo probabilmente rompe l'accoppiamento che @Aphex vuole. 'UpdateGUI' non ha senso senza essere chiamato nel contesto di' SubSystem' (o sottoclassi di 'SubSystem'), quindi dovrebbe essere un metodo statico o di classe. – cowbert

3

Hai sorta di risposto alla domanda nel chiedere che:. quale argomento vi aspettate di ottenere il self se si chiama SubSystem.UpdateGUI? Non c'è un'istanza ovvia che dovrebbe essere passata al decoratore.

Ci sono diverse cose che potresti fare per aggirare questo. Forse hai già un subSystem che hai istanziato da qualche altra parte? Allora si potrebbe usare il suo decoratore:

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

Ma forse non hai bisogno di l'istanza, in primo luogo, solo la classe SubSystem? In tal caso, utilizzare la classmethod decoratore di dire Python che questa funzione dovrebbe ricevere la sua classe come primo argomento, invece di un'istanza:

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

Infine, forse non hai bisogno di accedere sia alle istanza o classe ! In tal caso, utilizzare staticmethod:

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

Oh, a proposito, Python convenzione è quello di riservare nomi CamelCase per le classi e di utilizzare mixedCase o nomi under_scored per i metodi in quella classe.

+0

@'Aaron: vero, grazie. – katrielalex

+0

@ self.UpdateGUI non funziona, poiché self non ha significato nell'ambito della definizione della classe. Ecco perché ho usato il nome della classe base. – Aphex