Wie funktionieren Python-Eigenschaften?

78

Ich habe Python-Eigenschaften erfolgreich verwendet, sehe aber nicht, wie sie funktionieren könnten. Wenn ich eine Eigenschaft außerhalb einer Klasse dereferenziere, erhalte ich nur ein Objekt vom Typ property:

@property
def hello(): return "Hello, world!"

hello  # <property object at 0x9870a8>

Aber wenn ich eine Eigenschaft in eine Klasse einbaue, ist das Verhalten ganz anders:

class Foo(object):
   @property
   def hello(self): return "Hello, world!"

Foo().hello # 'Hello, world!'

Ich habe bemerkt, dass ungebunden Foo.helloimmer noch das propertyObjekt ist, also muss Klasseninstanziierung die Magie sein, aber welche Magie ist das?

Fred Foo
quelle
7
Siehe auch: Wie funktioniert der Dekorator @property?
Martin Thoma
ordne beide Fragen gut zu @MartinThoma, danke!
Kushan Gunasekera
3
@ MartinThoma max recursion depth exceeded... hahaa
Zain Arshad

Antworten:

53

Wie andere angemerkt haben, verwenden sie eine Sprachfunktion, die als Deskriptoren bezeichnet wird.

Der Grund, warum das eigentliche Eigenschaftsobjekt zurückgegeben wird, wenn Sie über eine Klasse darauf zugreifen, Foo.helloliegt darin, wie die Eigenschaft die __get__(self, instance, owner)spezielle Methode implementiert :

  • Wenn in einer Instanz auf einen Deskriptor zugegriffen wird , wird diese Instanz als entsprechendes Argument übergeben und ownerist die Klasse dieser Instanz.
  • Wenn über die Klasse darauf zugegriffen wird, instanceist None und wird nur ownerübergeben. Das propertyObjekt erkennt dies und kehrt zurück self.

Neben dem Deskriptor-Howto finden Sie auch die Dokumentation zum Implementieren von Deskriptoren und zum Aufrufen von Deskriptoren im Sprachhandbuch .

Tim Yates
quelle
24

Damit @properties ordnungsgemäß funktioniert, muss die Klasse eine Unterklasse von Objekten sein . Wenn die Klasse keine Unterklasse von Objekten ist, erstellt sie beim ersten Versuch, auf den Setter zuzugreifen, tatsächlich ein neues Attribut mit dem kürzeren Namen, anstatt über den Setter zuzugreifen.

Folgendes funktioniert nicht richtig.

class C(): # <-- Notice that object is missing

    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'getting value of x'
        return self._x

    @x.setter
    def x(self, x):
        print 'setting value of x'
        self._x = x

>>> c = C()
>>> c.x = 1
>>> print c.x, c._x
1 0

Folgendes wird korrekt funktionieren

class C(object):

    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'getting value of x'
        return self._x

    @x.setter
    def x(self, x):
        print 'setting value of x'
        self._x = x

>>> c = C()
>>> c.x = 1
setting value of x
>>> print c.x, c._x
getting value of x
1 1
Timothy Vann
quelle
30
Man sollte sich bewusst sein, dass ab Python 3 keine Vererbung vom Objekt mehr erforderlich ist.
DeFazer
8
Genauer gesagt, in Python 3 erben Klassen standardmäßig vom Objekt, sodass dies nicht explizit in den Code geschrieben werden muss.
Nathaniel Verhaaren
12

Eigenschaften sind Deskriptoren , und Deskriptoren verhalten sich besonders, wenn sie Mitglied einer Klasseninstanz sind. Kurz gesagt, wenn aes sich um eine Instanz vom Typ Aund A.fooeinen Deskriptor handelt, a.fooentspricht dies A.foo.__get__(a).

Sven Marnach
quelle
Tatsächlich scheinen sie auch mit einer alten Klasse zu arbeiten (in Python 2.7). Aber danke für den Link, werde lesen.
Fred Foo
Beachten Sie, dass die Methodensignatur für __get__()falsch ist. Es hat zwei Argumente (zusätzlich zu Selbst). Ansonsten gut erklärt.
Tim Yates
@larsmans: Auf der verlinkten Seite: "Beachten Sie, dass Deskriptoren nur für neue Stilobjekte oder Klassen aufgerufen werden." Ich erinnere mich auch, dass ich sie einmal für Klassen im alten Stil ausprobiert habe.
Sven Marnach
@ Tim: Das zweite Argument ist optional.
Sven Marnach
2
@larsmans: Ich habe ein paar schnelle Tests gemacht. Der Typ des Deskriptors selbst muss eine Klasse neuen Stils sein, sonst funktioniert er nicht. Die Klasse, die den Deskriptor enthält, spielt keine Rolle. Das obige Zitat könnte auf beide Arten gelesen werden.
Sven Marnach
2

Das propertyObjekt implementiert lediglich das Deskriptorprotokoll: http://docs.python.org/howto/descriptor.html

Achim
quelle
15
Ich verstehe sicherlich die Gründe für ein Dw in diesem Fall. Sowohl diese als auch die akzeptierte Antwort haben keine wirkliche Erklärung und sind im Grunde genommen nur Links. Während dies in einigen Fällen zur Fehlerbehebung ausreicht, scheint das OP nach einer menschlichen Erklärung zu diesem Thema gesucht zu haben.
Morgan Wilde