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?

21
demandé sur Aphex 2010-09-23 23:53:14

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
20
répondu kennytm 2010-09-23 19:58:41

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')
3
répondu unutbu 2010-09-23 20:00:55

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.

3
répondu Katriel 2010-09-23 20:11:57

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.

2
répondu aaronasterling 2010-09-23 20:20:06