Les décorateurs Python qui font partie d'une classe de base ne peuvent pas être utilisés pour décorer les fonctions membres dans les classes héritées
Les décorateurs Python sont amusants à utiliser, mais il semble que j'ai frappé un mur en raison de la façon dont les arguments sont passés aux décorateurs. Ici, j'ai un décorateur défini comme faisant partie d'une classe de base (le décorateur accédera aux membres de la classe, donc il nécessitera le paramètre self).
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
...
J'ai omis le reste de l'implémentation. Maintenant, cette classe est une classe de base pour divers sous-systèmes qui en hériteront - certaines des classes héritées devront utiliser UpdateGUI décorateur.
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
Encore une fois, j'ai omis les implémentations de fonction car elles ne sont pas pertinentes.
En bref, le problème est que même si je peux accéder au Décorateur défini dans la classe de base à partir de la classe héritée en le spécifiant comme sous-système.UpdateGUI, j'obtiens finalement cette TypeError en essayant de l'utiliser:
unbound method UpdateGUI() must be called with SubSystem instance as first argument (got function instance instead)
C'est parce que je n'ai aucun moyen immédiatement identifiable de passer le paramètre self
au Décorateur!
Y a-t-il un moyen de faire cette? Ou Ai-je atteint les limites de l'implémentation actuelle du décorateur en Python?
4 réponses
Vous devez faire UpdateGUI
Un @classmethod
, et rendre votre wrapper
conscient de self
. Un exemple de travail:
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
Il pourrait être plus facile de simplement retirer le décorateur de la classe SubSytem
:
(Notez que je suis en supposant que le self
qui appelle setport
est le même self
que vous souhaitez utiliser pour appeler 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')
Vous avez en quelque sorte répondu à la question en la posant: quel argument vous attendez-vous à obtenir comme self
Si vous appelez SubSystem.UpdateGUI
? Il n'y a pas une instance évidente qui devrait être passée au Décorateur.
Il y a plusieurs choses que vous pourriez faire pour contourner cela. Peut-être avez-vous déjà un subSystem
que vous avez instancié ailleurs? Ensuite, vous pouvez utiliser son décorateur:
subSystem = SubSystem()
subSystem.UpdateGUI(...)
Mais peut-être que vous n'aviez pas besoin de l'instance en premier lieu, juste la classe SubSystem
? Dans ce cas, utilisez le décorateur classmethod
pour indiquer à Python que cette fonction devrait recevoir sa classe comme premier argument au lieu d'une instance:
@classmethod
def UpdateGUI(cls,...):
...
Enfin, peut-être que vous n'avez pas besoin d'accéder à l'instance ou à la classe! Dans ce cas, utilisez staticmethod
:
@staticmethod
def UpdateGUI(...):
...
Oh, au fait, la convention Python est de réserver les noms CamelCase pour les classes et d'utiliser des noms mixedCase ou under_scored pour les méthodes de cette classe.
Vous devez utiliser une instance de SubSystem
pour faire votre décoration ou utiliser un classmethod
comme le suggère 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
Vous décidez lequel faire en décidant si vous voulez que toutes les instances de sous-classe partagent la même interface graphique ou si vous voulez pouvoir laisser des instances distinctes avoir des interfaces distinctes.
S'ils partagent tous la même interface graphique, utilisez une méthode de classe et faites tout ce que le décorateur accède à une instance de classe.
S'ils peuvent avoir des interfaces distinctes, vous besoin de décider si vous voulez représenter la distinction avec l'héritage (auquel cas vous utiliseriez également classmethod
et appelleriez le décorateur sur les sous-classes de SubSystem
) ou s'il est mieux représenté comme des instances distinctes. Dans ce cas, créez une instance pour chaque interface et appelez le décorateur sur cette instance.