Ich versuche zu verstehen, was Pythons Deskriptoren sind und wofür sie nützlich sind. Ich verstehe, wie sie funktionieren, aber hier sind meine Zweifel. Betrachten Sie den folgenden Code:
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?
Was ist
instance
undowner
hier? (in__get__
). Was ist der Zweck dieser Parameter?Wie würde ich dieses Beispiel aufrufen / verwenden?
quelle
self
undinstance
?self
ist die Deskriptorinstanz,instance
ist die Instanz der Klasse (falls instanziiert), in der sich der Deskriptor befindet (instance.__class__ is owner
).Temperature.celsius
gibt den Wert0.0
gemäß dem Code ancelsius = Celsius()
. Der Deskriptor Celsius wird aufgerufen, daher hat seine Instanz den Init-Wert,0.0
der dem Attribut Temperaturklasse celsius zugewiesen ist.Sie haben zusätzliche Kontrolle darüber, wie Attribute funktionieren. Wenn Sie beispielsweise in Java an Getter und Setter gewöhnt sind, ist dies Pythons Vorgehensweise. Ein Vorteil ist, dass es für Benutzer wie ein Attribut aussieht (die Syntax ändert sich nicht). Sie können also mit einem normalen Attribut beginnen und dann, wenn Sie etwas Besonderes tun müssen, zu einem Deskriptor wechseln.
Ein Attribut ist nur ein veränderbarer Wert. Mit einem Deskriptor können Sie beliebigen Code ausführen, wenn Sie einen Wert lesen oder festlegen (oder löschen). Sie können sich also vorstellen, damit ein Attribut beispielsweise einem Feld in einer Datenbank zuzuordnen - einer Art ORM.
Eine andere Verwendung könnte darin bestehen, die Annahme eines neuen Werts zu verweigern, indem eine Ausnahme
__set__
ausgelöst wird, wodurch das "Attribut" effektiv schreibgeschützt wird.Dies ist ziemlich subtil (und der Grund, warum ich hier eine neue Antwort schreibe - ich fand diese Frage, während ich mich das Gleiche fragte, und fand die vorhandene Antwort nicht so großartig).
Ein Deskriptor ist für eine Klasse definiert, wird jedoch normalerweise von einer Instanz aufgerufen. Wann ist es von einer Instanz namens beiden
instance
undowner
sind so eingestellt (und Sie können ausrechnenowner
aus ,instance
so dass es irgendwie sinnlos scheint). Aber wenn es von einer Klasse aufgerufen wird, wird nurowner
gesetzt - weshalb es da ist.Dies wird nur benötigt,
__get__
weil es das einzige ist, das für eine Klasse aufgerufen werden kann. Wenn Sie den Klassenwert festlegen, legen Sie den Deskriptor selbst fest. Ähnliches gilt zum Löschen. Deshalb wird dasowner
dort nicht benötigt.Hier ist ein cooler Trick mit ähnlichen Klassen:
(Ich verwende Python 3; für Python 2 müssen Sie sicherstellen, dass diese Unterteilungen
/ 5.0
und sind/ 9.0
). Das gibt:Jetzt gibt es andere, wohl bessere Möglichkeiten, um den gleichen Effekt in Python zu erzielen (z. B. wenn Celsius eine Eigenschaft wäre, die denselben grundlegenden Mechanismus darstellt, aber die gesamte Quelle in die Temperaturklasse einordnet), aber das zeigt, was getan werden kann ...
quelle
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
undstaticmethod
alle nutzt diese speziellen Methoden zur Kontrolle , wie sie über die gepunktete Lookup zugegriffen werden.Ein Datendeskriptor wie
property
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
undself
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ümerobjektobj_instance
mit einemdescriptor
Objekt:obj_instance.descriptor
ruft diedescriptor.__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 diedescriptor.__set__(self, obj_instance, value)
Rückgabe aufNone
So funktioniert das
setter
auf einer Eigenschaft.del obj_instance.descriptor
ruft diedescriptor.__delete__(self, obj_instance)
Rückgabe aufNone
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:
Ein Datendeskriptor hat ein
__set__
und / oder__delete__
.Ein Nicht-Daten-Deskriptor hat weder
__set__
noch__delete__
.Beispiele für eingebaute Deskriptorobjekte:
classmethod
staticmethod
property
Nicht-Daten-Deskriptoren
Wir können das sehen
classmethod
undstaticmethod
sind Nicht-Daten-Deskriptoren:Beide haben nur die
__get__
Methode:Beachten Sie, dass alle Funktionen auch Nicht-Daten-Deskriptoren sind:
Datenbeschreibung,
property
Ist
property
jedoch ein Daten-Deskriptor:Gepunktete Suchreihenfolge
Dies sind wichtige Unterscheidungen , da sie die Suchreihenfolge für eine gepunktete Suche beeinflussen.
obj_instance
's__dict__
befindetDie 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:
Ihr Deskriptor stellt sicher, dass Sie immer ein Float für dieses Klassenattribut von
Temperature
haben und dass Siedel
das Attribut nicht löschen können: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):
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: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.Hier ist eine Demonstration:
Sie können das Attribut nicht löschen:
Und Sie können keine Variable zuweisen, die nicht in einen Float konvertiert werden kann:
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):Welches hat genau das gleiche erwartete Verhalten des ursprünglichen Codeteils:
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.
quelle
Bevor Sie auf die Details der Deskriptoren eingehen, ist es möglicherweise wichtig zu wissen, wie die Attributsuche in Python funktioniert. Dies setzt voraus, dass die Klasse keine Metaklasse hat und die Standardimplementierung von verwendet
__getattribute__
(beide können zum "Anpassen" des Verhaltens verwendet werden).Die beste Darstellung der Attributsuche (in Python 3.x oder für Klassen neuen Stils in Python 2.x) ist in diesem Fall das Verständnis der Python-Metaklassen (Ionels Codelog) . Das Bild wird
:
als Ersatz für "nicht anpassbare Attributsuche" verwendet.Dies stellt die Suche eines Attributs
foobar
in eineminstance
von darClass
:Zwei Bedingungen sind hier wichtig:
instance
einen Eintrag für den Attributnamen hat__get__
und und__set__
.instance
hat keinen Eintrag für die Attributnamen , aber die Klasse hat eine , und es hat__get__
.Hier kommen Deskriptoren ins Spiel:
__get__
und haben__set__
.__get__
.In beiden Fällen wird der zurückgegebene Wert
__get__
mit der Instanz als erstem Argument und der Klasse als zweitem Argument aufgerufen.Die Suche ist noch komplizierter für die Suche nach Klassenattributen (siehe zum Beispiel die Suche nach Klassenattributen (im oben genannten Blog) ).
Kommen wir zu Ihren spezifischen Fragen:
In den meisten Fällen müssen Sie keine Deskriptorklassen schreiben! Sie sind jedoch wahrscheinlich ein sehr regelmäßiger Endbenutzer. Zum Beispiel Funktionen. Funktionen sind Deskriptoren. So können Funktionen als Methoden verwendet werden, die
self
implizit als erstes Argument übergeben werden.Wenn Sie
test_method
eine Instanz nachschlagen, erhalten Sie eine "gebundene Methode" zurück:In ähnlicher Weise können Sie eine Funktion auch binden, indem Sie ihre
__get__
Methode manuell aufrufen (nicht wirklich empfohlen, nur zur Veranschaulichung):Sie können diese "selbstgebundene Methode" sogar aufrufen:
Beachten Sie, dass ich keine Argumente angegeben habe und die Funktion die von mir gebundene Instanz zurückgegeben hat!
Funktionen sind Nicht-Daten-Deskriptoren !
Einige eingebaute Beispiele für einen Datendeskriptor wären
property
. Vernachlässigengetter
,setter
unddeleter
derproperty
Deskriptor ist (aus dem Descriptor HowTo Guide "Eigenschaften" ):Da es ein Datendeskriptor ist , ist es aufgerufen , wenn Sie den „Namen“ nachschlagen von der
property
und es einfach Delegierten zu den Funktionen dekoriert mit@property
,@name.setter
und@name.deleter
(falls vorhanden).In der Standardbibliothek gibt es mehrere andere Deskriptoren, z
staticmethod
.classmethod
.Der Punkt der Deskriptoren ist einfach (obwohl Sie sie selten benötigen): Abstrakter allgemeiner Code für den Attributzugriff.
property
ist eine Abstraktion für den Zugriff auf Variablen,function
bietet eine Abstraktion für Methoden,staticmethod
bietet eine Abstraktion für Methoden, die keinen Instanzzugriff benötigen, undclassmethod
eine Abstraktion für Methoden, die Klassenzugriff anstelle von Instanzzugriff benötigen (dies ist etwas vereinfacht).Ein anderes Beispiel wäre eine Klasseneigenschaft .
Ein lustiges Beispiel (mit
__set_name__
Python 3.6) könnte auch eine Eigenschaft sein, die nur einen bestimmten Typ zulässt:Dann können Sie den Deskriptor in einer Klasse verwenden:
Und ein bisschen damit spielen:
Oder eine "faule Eigenschaft":
Dies sind Fälle, in denen das Verschieben der Logik in einen gemeinsamen Deskriptor sinnvoll sein könnte, man sie jedoch auch mit anderen Mitteln lösen könnte (aber möglicherweise durch Wiederholen eines Codes).
Dies hängt davon ab, wie Sie das Attribut nachschlagen. Wenn Sie das Attribut einer Instanz nachschlagen, gehen Sie wie folgt vor:
Wenn Sie das Attribut für die Klasse nachschlagen (vorausgesetzt, der Deskriptor ist für die Klasse definiert):
None
Grundsätzlich ist das dritte Argument erforderlich, wenn Sie das Verhalten bei der Suche auf Klassenebene anpassen möchten (weil das
instance
istNone
).Ihr Beispiel ist im Grunde eine Eigenschaft, die nur Werte zulässt, in die konvertiert werden kann
float
und die von allen Instanzen der Klasse (und der Klasse gemeinsam genutzt werden) - obwohl man nur den Lesezugriff auf die Klasse verwenden kann, sonst würden Sie die Deskriptorinstanz ersetzen ):Aus diesem Grund verwenden Deskriptoren im Allgemeinen das zweite Argument (
instance
), um den Wert zu speichern und eine gemeinsame Nutzung zu vermeiden. In einigen Fällen kann es jedoch erwünscht sein, einen Wert zwischen Instanzen zu teilen (obwohl ich mir derzeit kein Szenario vorstellen kann). Es macht jedoch praktisch keinen Sinn für eine Celsius-Eigenschaft in einer Temperaturklasse ... außer vielleicht als rein akademische Übung.quelle
Inspiriert von Fluent Python von Buciano Ramalho
Stellen Sie sich vor, Sie haben eine Klasse wie diese
Wir sollten das Gewicht und den Preis validieren, um zu vermeiden, dass ihnen eine negative Zahl zugewiesen wird. Wir können weniger Code schreiben, wenn wir den Deskriptor als Proxy verwenden
Definieren Sie dann die Klasse LineItem wie folgt:
und wir können die Mengenklasse erweitern, um eine allgemeinere Validierung durchzuführen
quelle
weight = Quantity()
müssen Werte im Instanznamensraum nur mitself
(z. B.self.weight = 4
) festgelegt werden, sonst würde das Attribut auf den neuen Wert zurückgesetzt und der Deskriptor würde verworfen. Schön!weight = Quantity()
als Klassenvariable und ihre__get__
und__set__
arbeiten an der Instanzvariablen. Wie?Ich habe (mit geringfügigen Änderungen wie vorgeschlagen) den Code aus Andrew Cookes Antwort ausprobiert. (Ich verwende Python 2.7).
Der Code:
Das Ergebnis:
Stellen Sie bei Python vor 3 sicher, dass Sie eine Unterklasse von einem Objekt haben, damit der Deskriptor ordnungsgemäß funktioniert, da get get magic für Klassen im alten Stil nicht funktioniert.
quelle
Sie sehen https://docs.python.org/3/howto/descriptor.html#properties
quelle