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, classmethodund staticmethodalle 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 clsund selfbezeichnet) 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_instancemit einem descriptorObjekt:
obj_instance.descriptorruft die
descriptor.__get__(self, obj_instance, owner_class)Rückgabe von a auf. value
So funktionieren alle Methoden und die getEigenschaften einer Eigenschaft.
obj_instance.descriptor = valueruft die
descriptor.__set__(self, obj_instance, value)Rückgabe auf None
So funktioniert das setterauf einer Eigenschaft.
del obj_instance.descriptorruft die
descriptor.__delete__(self, obj_instance)Rückgabe auf None
So funktioniert das deleterauf einer Eigenschaft.
obj_instanceist die Instanz, deren Klasse die Instanz des Deskriptorobjekts enthält. selfist 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 classmethodund staticmethodsind 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 propertyjedoch 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 Temperaturehaben und dass Sie deldas 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?
instanceist 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 propertyDekorators, 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.
selfundinstance?