62 Stimmen

Überschreiben von Eigenschaften in python

Also, ich versuche herauszufinden, was der beste (eleganteste und mit dem geringsten Code) Weg ist, bestimmte Funktionen einer Eigenschaft zu überschreiben (z. B. nur den Getter, nur den Setter usw.) in Python. Ich bin ein Fan von der folgenden Methode, Eigenschaften zu definieren, da alle ihre Methoden im selben eingerückten Codeblock eingeschlossen sind (es ist einfacher zu sehen, wo die Funktionen einer Eigenschaft aufhören und die Funktionen der nächsten beginnen):

@apply
def foo():
    """Ein Foobar"""
    def fget(self):
        return self._foo
    def fset(self, val):
        self._foo = val
    return property(**locals())

Allerdings, wenn ich von einer Klasse erben möchte, die Eigenschaften auf diese Weise definiert, und dann zum Beispiel die Setter-Funktion von foo überschreiben möchte, scheint dies schwierig. Ich habe etwas recherchiert und die meisten Antworten, die ich gefunden habe, waren, separate Funktionen in der Basisklasse zu definieren (z. B. getFoo und setFoo), explizit eine Eigenschaftendefinition daraus zu erstellen (z. B. foo = property(lambda x: x.getFoo(), lambda x, y: x.setFoo(y), lambda x: x.delFoo())) und dann getFoo, setFoo und delFoo nach Bedarf zu überschreiben.

Diese Lösung gefällt mir nicht, denn es bedeutet, dass ich Lambdas für jede Eigenschaft definieren muss, und dann jeden Funktionsaufruf ausschreiben muss (während ich vorher einfach property(**locals()) hätte tun können). Außerdem erhalte ich nicht die ursprüngliche Kapselung.

Idealerweise würde ich gerne folgendes tun können:

class A(object):
    def __init__(self):
        self.foo = 8
    @apply
    def foo():
        """Ein Foobar"""
        def fget(self):
            return self._foo
        def fset(self, val):
            self._foo = val
        return property(**locals())

class ATimesTwo(A):
    @some_decorator
    def foo():
        def fset(self, val):
            self._foo = val * 2
        return something

Und dann würde die Ausgabe ungefähr so aussehen:

>>> a = A()
>>> a.foo
8
>>> b = ATimesTwo()
>>> b.foo
16

Grundsätzlich erbt ATimesTwo die Getter-Funktion von A, überschreibt aber die Setter-Funktion. Weiß jemand, wie man das machen kann (auf ähnliche Weise wie im obigen Beispiel)? Wie würde der some_decorator aussehen und was sollte die foo Funktion zurückgeben?

75voto

stderr Punkte 8311

Die Python Dokumentation zum property Decorator schlägt das folgende Idiom vor:

class C(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

Und dann können Unterklassen einen einzelnen Setter/Getter überschreiben, wie folgt:

class C2(C):
    @C.x.getter
    def x(self):
        return self._x * -1

Das ist ein wenig umständlich, da das Überschreiben mehrerer Methoden dazu führt, dass du etwas wie dies machen musst:

class C3(C):
    @C.x.getter
    def x(self):
        return self._x * -1
    # C3 hat jetzt eine x-Eigenschaft mit verändertem Getter
    # ändere also seinen Setter anstatt C.x' Setter.
    @x.setter 
    def x(self, value):
        self._x = value * 2

Natürlich, wenn du Getter, Setter und Deleter überschreibst, kannst du wahrscheinlich einfach die Property für C3 neu definieren.

61voto

agf Punkte 159286

Ich bin mir sicher, du hast das schon gehört, aber apply wurde seit acht Jahren abgelehnt, seit Python 2.3. Verwende es nicht. Deine Verwendung von locals() widerspricht ebenfalls dem Zen von Python -- explizit ist besser als implizit. Wenn dir die erhöhte Einrückung wirklich gefällt, musst du kein Wegwerfobjekt erstellen, sondern nur Folgendes tun:

if True:
    @property
    def foo(self):
        return self._foo
    @foo.setter
    def foo(self, val):
        self._foo = val

Das missbraucht nicht locals, verwendet nicht apply, erfordert nicht die Erstellung eines zusätzlichen Objekts und benötigt auch keine Zeile danach mit foo = foo(), was es schwieriger macht, das Ende des Blocks zu sehen. Es funktioniert genauso gut für deine altmodische Verwendung von property -- einfach foo = property(fget, fset) wie gewohnt machen.

Wenn du eine Eigenschaft in einer beliebigen Unterklasse überschreiben möchtest, kannst du ein Rezept wie dieses verwenden.

Wenn die Unterklasse weiß, wo die Eigenschaft definiert wurde, mache einfach:

class ATimesTwo(A):
    @A.foo.setter
    def foo(self, val):
        self._foo = val * 2

6voto

jusx Punkte 949

Die Antwort von stderr erfüllt die meisten Anwendungsfälle.

Ich möchte eine Lösung für den Fall hinzufügen, in dem Sie einen Getter, Setter und/oder Deleter erweitern möchten. Zwei Möglichkeiten, dies zu tun, sind:

1. Unterklasse property

Der erste Weg, dies zu tun, besteht darin, die integrierte property zu unterklassifizieren und Dekorateure hinzuzufügen, die Versionen von Getter, Setter und/oder Deleter sind, die die aktuellen Abruf-, Setz- und Löschcallbacks erweitern

Beispiel für eine Eigenschaft, die das Anhängen von Methoden an die Set-Funktionen unterstützt:

class ExtendableProperty(property):
    def append_setter(self, fset):
        # Erstelle einen Wrapper um den neuen fset, der auch den aktuellen fset aufruft
        _old_fset = self.fset

        def _appended_setter(obj, value):
            _old_fset(obj, value)
            fset(obj, value)
        # Verwende diesen Wrapper als Setter anstelle des neuen fset
        return self.setter(_appended_setter)

Die Verwendung ist genauso wie bei normalen Eigenschaften, nur jetzt ist es möglich, Methoden zu den Settern der Eigenschaft hinzuzufügen:

class A(object):
    @ExtendableProperty
    def prop(self):
        return self._prop

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

class B(A):
    @A.prop.append_setter
    def prop(self, v):
        print('Set', v)

>>> a = A()
>>> a.prop = 1
>>> a.prop
1

>>> b = B()
>>> b.prop = 1
Set 1
>>> b.prop
1

2. Überschreiben Sie Getter, Setter und/oder Deleter

Verwenden Sie eine normale Eigenschaft, überschreiben Sie den Getter, Setter oder Deleter und fügen dann Aufrufe zu fget, fset oder fdel in der Eigenschaft der Elternklasse hinzu.

Beispiel für den Typ der Eigenschaft wie im Beispiel 1:

class A(object):
    @property
    def prop(self):
        return self._prop

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

class B(A):
    @A.prop.setter
    def prop(self, v):
        A.prop.fset(self, v)  # Dies ist der Aufruf der ursprünglichen Set-Methode
        print('Set {}'.format(v))

Ich denke, die erste Möglichkeit sieht besser aus, weil der Aufruf des übergeordneten Eigenschafts-Fset nicht notwendig ist

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X