Wie rufe ich eine Eigenschaft der Basisklasse auf, wenn diese Eigenschaft in der abgeleiteten Klasse überschrieben wird?

87

Ich ändere einige meiner Klassen von einer umfangreichen Verwendung von Gettern und Setzern zu einer pythonischeren Verwendung von Eigenschaften.

Aber jetzt stecke ich fest, weil einige meiner vorherigen Getter oder Setter die entsprechende Methode der Basisklasse aufrufen und dann etwas anderes ausführen würden. Aber wie kann dies mit Eigenschaften erreicht werden? Wie rufe ich den Property Getter oder Setter in der übergeordneten Klasse auf?

Nur das Aufrufen des Attributs selbst führt natürlich zu einer unendlichen Rekursion.

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
OnkelZeiv
quelle

Antworten:

97

Sie könnten denken, Sie könnten die Basisklassenfunktion aufrufen, die von der Eigenschaft aufgerufen wird:

class FooBar(Foo):

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

Obwohl dies die naheliegendste Sache ist, denke ich - es funktioniert nicht, weil die Bar eine Eigenschaft ist, keine aufrufbare.

Eine Eigenschaft ist jedoch nur ein Objekt mit einer Getter-Methode, um das entsprechende Attribut zu finden:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)
David Cournapeau
quelle
Warum sollte ich Foo.bar.fset(self, c)einen geerbten Setter hinzuziehen? Warum nicht Foo.bar.fset(c)- ohne das "Selbst" - dachte ich, dies sei implizit vorbei?
Nerdoc
2
Ich erhalte einen TypeError: 'property'-Objekt kann nicht
aufgerufen werden
@nerdoc wo selfimpliziert das get aus der Kette Foo.bar.fset?
Tadhg McDonald-Jensen
Nur ein Gedanke - AFAIK Selbst wird immer implizit weitergegeben. Wenn Sie ein foo = Foo()\ foo.bar(c)ausführen, wird kein Selbst übergeben, aber bar () empfängt es von Python. Ich bin kein Python-Experte, mehr oder weniger auch ein Anfänger. Es ist nur ein Gedanke.
Nerdoc
selfwird nur implizit übergeben, wenn die Methode für eine Instanz einer Klasse aufgerufen wird. Wenn ich beispielsweise eine Klasse Amit einer Methode habe b(self, arg)und eine Instanz erstelle c = A(), c.b(arg)entspricht der AufrufA.b(c, arg)
TallChuck
53

Super sollte den Trick machen:

return super().bar

In Python 2.x müssen Sie die ausführlichere Syntax verwenden:

return super(FooBar, self).bar
Pankrat
quelle
1
TypeError: super () nimmt mindestens 1 Argument (0 angegeben)
Aaron Maenpaa
4
Ich denke (nun, nach dem Link zu urteilen: P), diese Antwort ist python3-bezogen. In Python3 kann super () null Argumente annehmen, ja.
Shylent
6
super (). bar scheint für den Getter gut zu funktionieren, funktioniert aber nicht für die Zuweisung über die Base-Eigenschaft in einem überschriebenen Setter. Wenn ich super (). Bar = 3 mache, erhalte ich AttributeError: 'super' Objekt hat kein Attribut 'bar'
Rob Smallshire
1
Guter Punkt, Rob, wusste das nicht. Hier sind weitere Informationen: stackoverflow.com/questions/10810369/…
Pankrat
2
Während dies zum Abrufen funktioniert, schlägt es zum Einstellen mit einem fehl AttributeError.
Ethan Furman
23

Es gibt eine Alternative super, bei der der Name der Basisklasse nicht explizit angegeben werden muss.

Basisklasse 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

In B Zugriff auf den Property Getter der übergeordneten Klasse A:

Wie andere bereits geantwortet haben, ist es:

super(B, self).prop

Oder in Python 3:

super().prop

Dies gibt den vom Getter der Eigenschaft zurückgegebenen Wert zurück, nicht den Getter selbst, aber es reicht aus, den Getter zu erweitern.

Zugriff auf den Eigenschaftssetzer der übergeordneten Klasse A in B:

Die beste Empfehlung, die ich bisher gesehen habe, ist die folgende:

A.prop.fset(self, value)

Ich glaube, das ist besser:

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

In diesem Beispiel sind beide Optionen gleichwertig, aber die Verwendung von Super hat den Vorteil, unabhängig von den Basisklassen von zu sein B. Wenn Sie Bvon einer CKlasse erben würden, die auch die Eigenschaft erweitert, müssten Sie den BCode nicht aktualisieren .

Vollständiger Code von B, der die Eigenschaft von A erweitert:

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)

Eine Einschränkung:

Sofern Ihre Eigenschaft keinen Setter hat, müssen Sie sowohl den Setter als auch den Getter definieren, Bauch wenn Sie nur das Verhalten eines dieser Setter ändern.

Maxime R.
quelle
Hallo, das ist sehr hilfreich. Ich habe eine Folgefrage dazu. Dieses Setup funktioniert gut; Es funktioniert jedoch nicht mehr, wenn ich init für B setze , um zusätzliche Eigenschaften zu definieren. Gibt es eine Möglichkeit, eine separate Init für B zu haben? Vielen Dank
Sang
Könnten Sie bitte erklären, wie super(B, self.__class__)genau mit funktioniert super(class, class)? Wo ist es dokumentiert?
Art
Ich habe in meiner Antwort
Eric
4

Versuchen

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

Obwohl ich nicht sicher bin, ob Python das Aufrufen der Basisklasseneigenschaft unterstützt. Eine Eigenschaft ist eigentlich ein aufrufbares Objekt, das mit der angegebenen Funktion eingerichtet wird und dann diesen Namen in der Klasse ersetzt. Dies könnte leicht bedeuten, dass keine Superfunktion verfügbar ist.

Sie können Ihre Syntax jedoch jederzeit ändern, um die Funktion property () zu verwenden:

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
quelle
Dies funktioniert gut, wenn Sie die Basisklasse schreiben. Was aber, wenn Sie eine Basisklasse eines Drittanbieters erweitern, die für die Eigenschaft und den Getter denselben Namen verwendet?
Akaihola
1

Einige kleine Verbesserungen an Maximes Antwort :

  • Verwenden __class__, um das Schreiben zu vermeiden B. Beachten Sie, dass dies self.__class__der Laufzeittyp von selfist, __class__ ohne self jedoch der Name der umschließenden Klassendefinition. super()ist eine Abkürzung für super(__class__, self).
  • Verwenden __set__statt fset. Letzteres ist spezifisch für propertys, Ersteres gilt jedoch für alle eigenschaftsähnlichen Objekte (Deskriptoren).
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
quelle
-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()

(es sei denn, ich vermisse etwas in Ihrer Erklärung)

shylent
quelle
1
Sie sind: er spricht über Eigenschaften, nicht einfache Methoden
Akaihola