Comment appeler une propriété de la classe de base si cette propriété est en cours de remplacement dans la classe dérivée?

87

Je change certaines de mes classes d'une utilisation extensive des getters et setters à une utilisation plus pythonique des propriétés.

Mais maintenant je suis bloqué parce que certains de mes précédents getters ou setters appelleraient la méthode correspondante de la classe de base, puis effectueraient autre chose. Mais comment cela peut-il être accompli avec des propriétés? Comment appeler la propriété getter ou setter dans la classe parent?

Bien sûr, le simple fait d'appeler l'attribut lui-même donne une récursion infinie.

class Foo(object):

    @property
    def bar(self):
        return 5

    @bar.setter
    def bar(self, a):
        print a

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return self.bar # --> recursion!

    @bar.setter
    def bar(self, c):
        # perform the same action
        # as in the base class
        self.bar = c    # --> recursion!
        # then do something else
        print 'something else'

fb = FooBar()
fb.bar = 7
OncleZeiv
la source

Réponses:

100

Vous pourriez penser que vous pourriez appeler la fonction de classe de base qui est appelée par propriété:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar(self)

Bien que ce soit la chose la plus évidente à essayer, je pense - cela ne fonctionne pas car le bar est une propriété, pas un appelable.

Mais une propriété n'est qu'un objet, avec une méthode getter pour trouver l'attribut correspondant:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)
David Cournapeau
la source
Pourquoi devrais-je appeler Foo.bar.fset(self, c)un passeur hérité? Pourquoi pas Foo.bar.fset(c)- sans le «moi» - je pensais que c'était implicitement passé?
nerdoc
2
J'obtiens un TypeError: l'objet 'property' n'est pas appelable
hithwen
@nerdoc d'où vient le selfget implique de la chaîne Foo.bar.fset?
Tadhg McDonald-Jensen
Juste une pensée - le moi AFAIK est toujours implicitement passé. Si vous faites un foo = Foo()\ foo.bar(c), aucun self n'est passé, mais bar () le reçoit de Python. Je ne suis pas un expert Python, plus ou moins un débutant aussi. C'est juste une pensée.
nerdoc
selfn'est transmise implicitement que lorsque la méthode est appelée sur une instance d'une classe. Par exemple, si j'ai une classe Aavec une méthode b(self, arg)et que je crée une instance c = A(), alors l'appel c.b(arg)est équivalent àA.b(c, arg)
TallChuck
53

Super devrait faire l'affaire:

return super().bar

Dans Python 2.x, vous devez utiliser la syntaxe la plus verbeuse:

return super(FooBar, self).bar
Pankrat
la source
1
TypeError: super () prend au moins 1 argument (0 donné)
Aaron Maenpaa
4
Je suppose (enfin, à en juger par le lien: P), cette réponse est liée à python3. En python3, super () peut prendre zéro argument, oui.
shylent
6
super (). bar semble fonctionner correctement pour le getter, mais ne fonctionne pas pour l'affectation via la propriété de base dans un setter surchargé. Si je fais super (). Bar = 3 j'obtiens AttributeError: l'objet 'super' n'a pas d'attribut 'bar'
Rob Smallshire
1
Bon point Rob, ne le savait pas. Voici plus d'informations: stackoverflow.com/questions/10810369/…
Pankrat
2
Bien que cela fonctionne pour obtenir, il échoue pour le réglage avec un AttributeError.
Ethan Furman
24

Il existe une alternative d'utilisation superqui ne nécessite pas de référencer explicitement le nom de la classe de base.

Classe de base A:

class A(object):
    def __init__(self):
        self._prop = None

    @property
    def prop(self):
        return self._prop

    @prop.setter
    def prop(self, value):
        self._prop = value

class B(A):
    # we want to extend prop here
    pass

En B, accéder au getter de propriété de la classe parent A:

Comme d'autres l'ont déjà répondu, c'est:

super(B, self).prop

Ou en Python 3:

super().prop

Cela retourne la valeur retournée par le getter de la propriété, pas le getter lui-même mais c'est suffisant pour étendre le getter.

En B, accéder au setter de propriétés de la classe parent A:

La meilleure recommandation que j'ai vue jusqu'à présent est la suivante:

A.prop.fset(self, value)

Je pense que celui-ci est meilleur:

super(B, self.__class__).prop.fset(self, value)

Dans cet exemple, les deux options sont équivalentes mais l'utilisation de super a l'avantage d'être indépendante des classes de base de B. Si vous deviez Bhériter d'une Cclasse étendant également la propriété, vous n'auriez pas à mettre à jour Ble code de.

Code complet de B étendant la propriété de A:

class B(A):
    @property
    def prop(self):
        value = super(B, self).prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(B, self.__class__).prop.fset(self, value)

Une mise en garde:

Sauf si votre propriété n'a pas de setter, vous devez définir à la fois le setter et le getter Bmême si vous ne modifiez que le comportement de l'un d'eux.

Maxime R.
la source
Bonjour, c'est très utile. J'ai une question complémentaire à ce sujet. Cette configuration fonctionne bien; cependant, il cesse de fonctionner lorsque je configure init pour que B définisse des propriétés supplémentaires. Existe-t-il un moyen d'avoir un init séparé pour B? Merci
Sang
Pourriez-vous s'il vous plaît expliquer comment super(B, self.__class__)fonctionne exactement avec super(class, class)? Où est-il documenté?
Art
J'ai suggéré quelques petits ajustements à cette réponse dans ma réponse
Eric
4

essayer

@property
def bar:
    return super(FooBar, self).bar

Bien que je ne sois pas sûr que python prenne en charge l'appel de la propriété de classe de base. Une propriété est en fait un objet appelable qui est configuré avec la fonction spécifiée, puis remplace ce nom dans la classe. Cela pourrait facilement signifier qu'il n'y a pas de super fonction disponible.

Vous pouvez toujours changer votre syntaxe pour utiliser la fonction property ():

class Foo(object):

    def _getbar(self):
        return 5

    def _setbar(self, a):
        print a

    bar = property(_getbar, _setbar)

class FooBar(Foo):

    def _getbar(self):
        # return the same value
        # as in the base class
        return super(FooBar, self)._getbar()

    def bar(self, c):
        super(FooBar, self)._setbar(c)
        print "Something else"

    bar = property(_getbar, _setbar)

fb = FooBar()
fb.bar = 7
workmad3
la source
Cela fonctionne bien si vous écrivez la classe de base. Mais que faire si vous étendez une classe de base tierce qui utilise le même nom pour la propriété et le getter?
akaihola le
1

Quelques petites améliorations à la réponse de Maxime :

  • Utiliser __class__pour éviter d'écrire B. Notez qu'il self.__class__s'agit du type d'exécution de self, mais __class__ sans self le nom de la définition de classe englobante. super()est un raccourci pour super(__class__, self).
  • Utiliser __set__au lieu de fset. Ce dernier est spécifique à propertys, mais le premier s'applique à tous les objets de type propriété (descripteurs).
class B(A):
    @property
    def prop(self):
        value = super().prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(__class__, self.__class__).prop.__set__(self, value)
Eric
la source
0

Vous pouvez utiliser le modèle suivant:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1
  
class Child(Parent):

    #getter
    @property
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)

Remarque! Toutes les méthodes de propriété doivent être redéfinies ensemble. Si vous ne souhaitez pas redéfinir toutes les méthodes, utilisez plutôt le modèle suivant:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1


class Child(Parent):

    #getter
    @Parent.prop1.getter
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @Parent.prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @Parent.prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)
Nader Aryabarzan
la source
-4
    class Base(object):
      def method(self):
        print "Base method was called"

    class Derived(Base):
      def method(self):
        super(Derived,self).method()
        print "Derived method was called"

    d = Derived()
    d.method()

(à moins que je ne manque quelque chose dans votre explication)

timide
la source
1
vous êtes: il parle de propriétés, pas de méthodes simples
akaihola