Ich versuche zu verstehen, was Pythons Deskriptoren sind und wofür sie nützlich sein können.
Deskriptoren sind Klassenattribute (wie Eigenschaften oder Methoden) mit einer der folgenden speziellen Methoden:
__get__
(Nicht-Daten-Deskriptor-Methode, zum Beispiel für eine Methode / Funktion)
__set__
(Datendeskriptormethode, zum Beispiel für eine Eigenschaftsinstanz)
__delete__
(Datendeskriptormethode)
Diese Deskriptorobjekte können als Attribute für andere Objektklassendefinitionen verwendet werden. (Das heißt, sie leben im __dict__
Objekt der Klasse.)
Deskriptorobjekte können verwendet werden, um die Ergebnisse einer gepunkteten Suche (z. B. foo.descriptor
) in einem normalen Ausdruck, einer Zuweisung und sogar einer Löschung programmgesteuert zu verwalten .
Funktionen / Methoden, gebundene Methoden property
, classmethod
und staticmethod
alle nutzt diese speziellen Methoden zur Kontrolle , wie sie über die gepunktete Lookup zugegriffen werden.
Ein Datendeskriptor wieproperty
kann für faule Auswertung von Attributen auf einem einfacheren Zustand des Objekts ermöglichen basieren, so dass Instanzen als weniger Speicher verwenden , wenn Sie jedes mögliche Attribut vorberechnet.
Ein anderer Datendeskriptor , a member_descriptor
, erstellt von __slots__
, ermöglicht Speichereinsparungen, indem die Klasse Daten in einer veränderlichen tupelartigen Datenstruktur speichern kann, anstatt flexibler, aber platzaufwendiger __dict__
.
Nicht-Daten - Deskriptoren, in der Regel beispielsweise Klasse und statische Methoden, erhalten ihre Argumente impliziten ersten ( in der Regel genannt cls
und self
bezeichnet) von ihrer nicht-Datendeskriptor Methode __get__
.
Die meisten Benutzer von Python müssen nur die einfache Verwendung lernen und müssen die Implementierung von Deskriptoren nicht weiter lernen oder verstehen.
Im Detail: Was sind Deskriptoren?
Ein Deskriptor ist ein Objekt mit einem der folgenden Verfahren ( __get__
, __set__
oder __delete__
), bestimmt mittels gepunkteter lookup verwendet werden , als ob es ein typisches Merkmal einer Instanz waren. Für ein Eigentümerobjekt obj_instance
mit einem descriptor
Objekt:
obj_instance.descriptor
ruft die
descriptor.__get__(self, obj_instance, owner_class)
Rückgabe von a auf. value
So funktionieren alle Methoden und die get
Eigenschaften einer Eigenschaft.
obj_instance.descriptor = value
ruft die
descriptor.__set__(self, obj_instance, value)
Rückgabe auf None
So funktioniert das setter
auf einer Eigenschaft.
del obj_instance.descriptor
ruft die
descriptor.__delete__(self, obj_instance)
Rückgabe auf None
So funktioniert das deleter
auf einer Eigenschaft.
obj_instance
ist die Instanz, deren Klasse die Instanz des Deskriptorobjekts enthält. self
ist die Instanz des Deskriptors (wahrscheinlich nur eine für die Klasse derobj_instance
)
Um dies mit Code zu definieren, ist ein Objekt ein Deskriptor, wenn sich die Menge seiner Attribute mit einem der erforderlichen Attribute überschneidet:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
Ein Datendeskriptor hat ein __set__
und / oder __delete__
.
Ein Nicht-Daten-Deskriptor hat weder __set__
noch __delete__
.
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
Beispiele für eingebaute Deskriptorobjekte:
classmethod
staticmethod
property
- Funktionen im Allgemeinen
Nicht-Daten-Deskriptoren
Wir können das sehen classmethod
und staticmethod
sind Nicht-Daten-Deskriptoren:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
Beide haben nur die __get__
Methode:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
Beachten Sie, dass alle Funktionen auch Nicht-Daten-Deskriptoren sind:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Datenbeschreibung, property
Ist property
jedoch ein Daten-Deskriptor:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
Gepunktete Suchreihenfolge
Dies sind wichtige Unterscheidungen , da sie die Suchreihenfolge für eine gepunktete Suche beeinflussen.
obj_instance.attribute
- Zunächst wird geprüft, ob das Attribut ein Data-Descriptor für die Klasse der Instanz ist.
- Wenn nicht, wird geprüft, ob sich das Attribut in den
obj_instance
's __dict__
befindet
- es fällt schließlich auf einen Nicht-Daten-Deskriptor zurück.
Die Folge dieser Suchreihenfolge ist, dass Nicht-Daten-Deskriptoren wie Funktionen / Methoden von Instanzen überschrieben werden können .
Rückblick und nächste Schritte
Wir haben gelernt , dass Deskriptoren sind Objekte mit einem __get__
, __set__
oder __delete__
. Diese Deskriptorobjekte können als Attribute für andere Objektklassendefinitionen verwendet werden. Nun werden wir uns am Beispiel Ihres Codes ansehen, wie sie verwendet werden.
Analyse des Codes aus der Frage
Hier ist Ihr Code, gefolgt von Ihren Fragen und Antworten zu jedem:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- Warum brauche ich die Deskriptorklasse?
Ihr Deskriptor stellt sicher, dass Sie immer ein Float für dieses Klassenattribut von Temperature
haben und dass Sie del
das Attribut nicht löschen können:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Andernfalls ignorieren Ihre Deskriptoren die Eigentümerklasse und die Instanzen des Eigentümers und speichern stattdessen den Status im Deskriptor. Sie können den Status mit einem einfachen Klassenattribut genauso einfach für alle Instanzen freigeben (sofern Sie ihn immer als Float für die Klasse festlegen und ihn niemals löschen oder mit Benutzern Ihres Codes vertraut sind):
class Temperature(object):
celsius = 0.0
Dadurch erhalten Sie genau das gleiche Verhalten wie in Ihrem Beispiel (siehe Antwort auf Frage 3 unten), verwenden jedoch ein integriertes Pythons ( property
) und werden als idiomatischer angesehen:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- Was ist Instanz und Eigentümer hier? (in get ). Was ist der Zweck dieser Parameter?
instance
ist die Instanz des Besitzers, der den Deskriptor aufruft. Der Eigentümer ist die Klasse, in der das Deskriptorobjekt zum Verwalten des Zugriffs auf den Datenpunkt verwendet wird. Weitere beschreibende Variablennamen finden Sie in den Beschreibungen der speziellen Methoden, die Deskriptoren neben dem ersten Absatz dieser Antwort definieren.
- Wie würde ich dieses Beispiel aufrufen / verwenden?
Hier ist eine Demonstration:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
Sie können das Attribut nicht löschen:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Und Sie können keine Variable zuweisen, die nicht in einen Float konvertiert werden kann:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
Andernfalls haben Sie hier einen globalen Status für alle Instanzen, der durch Zuweisen zu einer beliebigen Instanz verwaltet wird.
Die erwartete Art und Weise, wie die meisten erfahrenen Python-Programmierer dieses Ergebnis erzielen würden, wäre die Verwendung des property
Dekorators, der dieselben Deskriptoren unter der Haube verwendet, aber das Verhalten in die Implementierung der Eigentümerklasse einbringt (wieder wie oben definiert):
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
Welches hat genau das gleiche erwartete Verhalten des ursprünglichen Codeteils:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
Fazit
Wir haben die Attribute behandelt, die Deskriptoren definieren, den Unterschied zwischen Daten- und Nicht-Daten-Deskriptoren, integrierte Objekte, die sie verwenden, und spezifische Fragen zur Verwendung.
Wie würden Sie das Beispiel der Frage verwenden? Ich hoffe du würdest nicht. Ich hoffe, Sie beginnen mit meinem ersten Vorschlag (einem einfachen Klassenattribut) und fahren mit dem zweiten Vorschlag (dem Eigenschaftsdekorateur) fort, wenn Sie dies für notwendig halten.
self
undinstance
?