7 Stimmen

Wo kommt der Zustand eines Python-Deskriptors hin?

Hintergrund

Ich versuche, Python's Deskriptoren zu verstehen, indem ich Lutz's Learning Python Abschnitt zum Thema lese, in dem er sagt: "Wie Eigenschaften sind Deskriptoren so konzipiert, dass sie spezifische Attribute behandeln... Im Gegensatz zu Eigenschaften haben Deskriptoren ihren eigenen Zustand..."

Im Laufe des Kapitels zeigt er Beispiele, bei denen das verwaltete Attribut tatsächlich im enthaltenden/umhüllenden Objekt gespeichert ist, wie zum Beispiel:

def __set__(self, Instance, Wert):
    Instance._name = Wert.lower()

Ich verstehe diese Beispiele und sie scheinen in Schreibweisen zum Thema häufig vorzukommen. Dennoch ist ihr Nutzen gegenüber Eigenschaften für mich nicht offensichtlich und sie scheinen nicht den internen Zustand zu haben, wie im obigen Zitat versprochen.

Am Ende des Kapitels zeigt er ein Beispiel, das näher an dem ist, was ich nach dem Lesen von "eigenen Zustand haben" erwartet habe, wie zum Beispiel:

def __set__(self, Instance, Wert):
    self.name = Wert.lower()

Das Beispiel läuft, macht aber nicht, was ich erwarten würde. Da das Beispiel ein wenig lang ist, habe ich es auf Pastebin gestellt und eine letzte Zeile hinzugefügt, die das unerwartete Verhalten zeigt (Bob's Name ist jetzt Sue). Hier ist ein kürzeres Demosnippet:

Klasse Wrapper(objekt):
    Klasse BeispielDeskriptor(objekt):
        def __get__(self, Instanz, Besitzer):
            print "holen %s" % self.state
            return self.state

        def __set__(self, Instanz, Wert):
            print "setzen %s" % Wert
            self.state = Wert
    ex = BeispielDeskriptor()

w1 = Wrapper()
w1.ex = 1
print w1.ex
w2 = Wrapper()
print w2.ex
w2.ex = 2
print  w1.ex
print w1.ex is w2.ex

Der Output davon ist:

setzen 1 
holen 1
1
holen 1
1
setzen 2
holen 2
2
holen 2
holen 2
True

Nach genauerem Betrachten des Codes ist keiner dieser Ausführungen eine Überraschung. Die Validierungslogik im Deskriptor macht aus diesem Attribut auf der Wrapper-Klasse de facto ein Singleton; es ist jedoch schwer vorstellbar, dass dieser gemeinsame Zustand Lutz's Absicht war oder die Absicht in diesem weithin verlinkten Tutorial zum Thema war.

Frage

Ist es möglich, einen Deskriptor zu erstellen, der einen internen Zustand hat, der einzigartig für das umhüllende Objekt ist, ohne diesen Zustand auf den umhüllenden Objektinstanzen zu speichern (wie im ersten Snippet)? Ist es möglich, die CardHolder-Klasse aus dem verlinkten Beispiel so zu ändern, dass Bob nicht am Ende als Sue endet?

2voto

Ethan Furman Punkte 57238

"Wie Eigenschaften sind Deskriptoren dazu gedacht, spezifische Attribute zu verwalten... Im Gegensatz zu Eigenschaften haben Deskriptoren ihren eigenen Zustand..."

Ich bin mir nicht sicher, welche Aussage Lutz treffen will, da Eigenschaften tatsächlich selbst Deskriptoren sind.

Aber obwohl Deskriptoren ihren eigenen Zustand haben, ist dies nicht besonders nützlich, da Sie, wie Sie festgestellt haben, nur ein Deskriptorobjekt pro Klassenattribut erhalten, anstatt eins pro Instanz. Daher wird die Instanz übergeben, damit instanzspezifische Werte gespeichert/abgerufen werden können.

Um zu beweisen, dass es sich um ein Deskriptorobjekt pro Attribut handelt, können Sie diesen leicht modifizierten Code aus einem Ihrer Links ausprobieren:

class RevealAccess(object):
    """Ein Daten-Deskriptor, der Werte setzt und zurückgibt
       normalerweise und eine Meldung druckt, wenn auf sie zugegriffen wird.
    """
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name
    def __get__(self, obj, objtype):
        print 'Abrufen', self.name
        return self.val
    def __set__(self, obj, val):
        print 'Aktualisieren' , self.name
        self.val = val

class MyClass(object):
    x = RevealAccess(10, 'var "x"')
    y = RevealAccess(5, 'var "y"')

m = MyClass()
m.x
m.x = 20
m.x
m.y

Was Sie sehen sollten:

Abrufen var "x"
Aktualisieren var "x"
Abrufen var "x"
Abrufen var "y"

Um Ihre Frage zu beantworten: Ja. Aber es ist mühsam.

class Stored(object):
    """Ein Daten-Deskriptor, der Instanzwerte in sich selbst speichert.
    """
    instances = dict()
    def __init__(self, val):
        self.instances[self, None] = val
    def __get__(self, obj, objtype):
        return self.instances[self, obj]
    def __set__(self, obj, val):
        self.instances[self, obj] = val

class MyClass(object):
    x = Stored(3)
    y = Stored(9)

print(MyClass.x)
print(MyClass.y)
m = MyClass()
m.x = 42
print(m.x)
m.y = 19
print(m.y)
print(m.x)

0 Stimmen

Ich denke, du meinst self.instances[obj]. Beachten Sie, dass Sie durch Speichern einer Referenz auf eine Instanz im Descriptor eine Zirkelreferenz erstellt haben. Wenn Sie die Instanz tatsächlich benötigen, können Sie einfach self.instances[id(obj)] speichern, anstatt sich um weakrefs zu sorgen.

0 Stimmen

@MichaelMerickel, eigentlich habe ich das zuerst ausprobiert, mit dem Ergebnis, dass sowohl x als auch y gleich waren. Um einen eindeutigen Schlüssel zu haben, sind sowohl self als auch obj erforderlich. Der Deskriptor wird in der Klasse gespeichert, nicht in der Instanz, daher sollte es keine zirkuläre Referenz geben.

0 Stimmen

@Ethan Danke. Das Erstellen eines Wörterbuchs innerhalb der Deskriptorklasse und das manuelle Verwalten des Zustands entspricht klar einem Schmerz. Ich vermute, es ist am besten, Eigenschaften als eine einfachere API um die Kernbeschreibungsimplementierung herum zu betrachten, anstatt zu versuchen, herauszufinden, was anscheinend zwei konkurrierende Möglichkeiten sind, dieselbe Sache innerhalb der Sprache zu tun.

1voto

Michael Merickel Punkte 22953

Wie Sie bereits erwähnt haben, ist ein Deskriptor eine Klasseninstanz, sodass sein Zustand zwischen jeder Instanz der Klasse gemeinsam genutzt wird.

Der Deskriptor könnte einen internen Hash der Instanzen speichern, die er umschließt. Um Kreisreferenzen zu vermeiden, wäre es klüger, den Schlüssel als id der Instanz zu haben. Der einzige Grund, den ich sehe, um dies zu tun, ist, wenn der Zweck des Deskriptors darin besteht, diese Eigenschaften aus verschiedenen Instanzen zu aggregieren.

Was den zweiten Teil Ihrer Frage betrifft, tun Sie einfach, wie Sie bereits gesagt haben, und speichern Sie den zugrunde liegenden Zustand auf der Instanz anstelle des Deskriptors, und dann wird Bob nicht Sue sein.

0 Stimmen

> den Zustand auf der Instanz speichern. Warum also einen Deskriptor verwenden, anstatt Eigenschaften zu nutzen?

0 Stimmen

Wenn Sie mit Eigenschaften die Verwendung von @property meinen, hoffentlich können Sie zu diesem Zeitpunkt verstehen, dass es sich selbst um einen Deskriptor handelt. Deskriptoren werden stark genutzt, um Funktionalität bereitzustellen, wenn man sowohl mit einem Objekt als auch mit einer Klasse umgeht, da sie feststellen können, ob Instanz None in den verschiedenen Aufrufen ist.

0 Stimmen

Können Sie den zweiten Satz genauer erläutern?

1voto

Tobu Punkte 23823

(Eine Ergänzung zu anderen Antworten) Um einem Objekt Zustand hinzuzufügen, das Sie nicht kontrollieren, ohne es zu stören, verwenden Sie einfach einen schwachen Container; weakref.WeakKeyDictionary ist hier angebracht. Der Garbage Collector wird dafür sorgen, dass der zusätzliche Zustand des Deskriptors nicht nach dem Sammeln der Instanzen verbleibt und dass der Deskriptor die Instanzen nicht länger leben lässt als normalerweise.

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