Einführung
Das Python-Objektsystem basiert weitgehend auf einer Implementierung, die auf Wörterbüchern beruht. In diesem Abschnitt wird dies diskutiert.
This tutorial is from open-source community. Access the source code
💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken
Das Python-Objektsystem basiert weitgehend auf einer Implementierung, die auf Wörterbüchern beruht. In diesem Abschnitt wird dies diskutiert.
Denken Sie daran, dass ein Wörterbuch eine Sammlung von benanntenen Werten ist.
stock = {
'name' : 'GOOG',
'shares' : 100,
'price' : 490.1
}
Wörterbücher werden üblicherweise für einfache Datenstrukturen verwendet. Sie werden jedoch für kritische Teile des Interpreters verwendet und können der wichtigste Datentyp in Python sein.
Innerhalb eines Moduls enthält ein Wörterbuch alle globalen Variablen und Funktionen.
## foo.py
x = 42
def bar():
...
def spam():
...
Wenn Sie foo.__dict__
oder globals()
untersuchen, werden Sie das Wörterbuch sehen.
{
'x' : 42,
'bar' : <function bar>,
'spam' : <function spam>
}
Benutzerdefinierte Objekte verwenden ebenfalls Wörterbücher für Instanzdaten und Klassen. Tatsächlich besteht das gesamte Objektsystem größtenteils aus einer zusätzlichen Schicht, die auf Wörterbüchern aufgesetzt ist.
Ein Wörterbuch enthält die Instanzdaten, __dict__
.
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{'name' : 'GOOG','shares' : 100, 'price': 490.1 }
Sie füllen dieses Dict (und die Instanz) bei der Zuweisung an self
.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Die Instanzdaten, self.__dict__
, sehen so aus:
{
'name': 'GOOG',
'shares': 100,
'price': 490.1
}
Jede Instanz bekommt ihr eigenes privates Wörterbuch.
s = Stock('GOOG', 100, 490.1) ## {'name' : 'GOOG','shares' : 100, 'price': 490.1 }
t = Stock('AAPL', 50, 123.45) ## {'name' : 'AAPL','shares' : 50, 'price': 123.45 }
Wenn Sie 100 Instanzen einer Klasse erzeugen, gibt es 100 Wörterbücher, die Daten speichern.
Ein separates Wörterbuch enthält auch die Methoden.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
Das Wörterbuch befindet sich in Stock.__dict__
.
{
'cost': <function>,
'sell': <function>,
'__init__': <function>
}
Instanzen und Klassen sind miteinander verknüpft. Das Attribut __class__
verweist zurück auf die Klasse.
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{ 'name': 'GOOG','shares': 100, 'price': 490.1 }
>>> s.__class__
<class '__main__.Stock'>
>>>
Das Instanzwörterbuch enthält Daten, die für jede Instanz einzigartig sind, während das Klassenwörterbuch Daten enthält, die von allen Instanzen gemeinsam geteilt werden.
Wenn Sie mit Objekten arbeiten, greifen Sie auf Daten und Methoden mit dem .
-Operator zu.
x = obj.name ## Abrufen
obj.name = value ## Festlegen
del obj.name ## Löschen
Diese Operationen sind direkt an die darunter liegenden Wörterbücher gebunden.
Operationen, die ein Objekt ändern, aktualisieren das zugrunde liegende Wörterbuch.
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{ 'name':'GOOG','shares': 100, 'price': 490.1 }
>>> s.shares = 50 ## Festlegen
>>> s.date = '6/7/2007' ## Festlegen
>>> s.__dict__
{ 'name': 'GOOG','shares': 50, 'price': 490.1, 'date': '6/7/2007' }
>>> del s.shares ## Löschen
>>> s.__dict__
{ 'name': 'GOOG', 'price': 490.1, 'date': '6/7/2007' }
>>>
Nehmen wir an, Sie lesen ein Attribut auf einer Instanz.
x = obj.name
Das Attribut kann an zwei Stellen vorhanden sein:
Beide Wörterbücher müssen überprüft werden. Zunächst wird im lokalen __dict__
nachgeschaut. Wenn es nicht gefunden wird, wird im __dict__
der Klasse über __class__
gesucht.
>>> s = Stock(...)
>>> s.name
'GOOG'
>>> s.cost()
49010.0
>>>
Dieses Suchschema ist die Art und Weise, wie die Mitglieder einer Klasse von allen Instanzen geteilt werden.
Klassen können von anderen Klassen erben.
class A(B, C):
...
Die Basisklassen werden in einem Tupel in jeder Klasse gespeichert.
>>> A.__bases__
(<class '__main__.B'>, <class '__main__.C'>)
>>>
Dies liefert einen Link zu den Elternklassen.
Logisch gesehen verläuft der Prozess zum Finden eines Attributs wie folgt. Zunächst wird im lokalen __dict__
nachgeschaut. Wenn es nicht gefunden wird, wird im __dict__
der Klasse gesucht. Wenn es in der Klasse nicht gefunden wird, wird in den Basisklassen über __bases__
gesucht. Es gibt jedoch einige subtilere Aspekte, die im Folgenden besprochen werden.
In Vererbungs hierarchien werden Attribute indem man in der Reihenfolge den Vererbungsbaum hinauf läuft, gefunden.
class A: pass
class B(A): pass
class C(A): pass
class D(B): pass
class E(D): pass
Bei einfacher Vererbung gibt es einen einzigen Pfad zum obersten Element. Man stoppt bei der ersten Übereinstimmung.
Python berechnet eine Vererbungsfolge im Voraus und speichert sie im MRO-Attribut der Klasse. Sie können es anzeigen.
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.D'>,
<class '__main__.B'>, <class '__main__.A'>,
<type 'object'>)
>>>
Diese Folge wird als Method Resolution Order bezeichnet. Um ein Attribut zu finden, läuft Python die MRO in der angegebenen Reihenfolge ab. Die erste Übereinstimmung gewinnt.
Beim Mehrfachvererbung gibt es keinen einzigen Pfad zum obersten Element. Schauen wir uns ein Beispiel an.
class A: pass
class B: pass
class C(A, B): pass
class D(B): pass
class E(C, D): pass
Was passiert, wenn Sie auf ein Attribut zugreifen?
e = E()
e.attr
Es wird ein Attributsucheprozess durchgeführt, aber welche ist die Reihenfolge? Das ist ein Problem.
Python verwendet kooperatives Mehrfachvererbung, die bestimmte Regeln zur Klasseneinordnung befolgt.
Die MRO wird berechnet, indem alle Klassen in einer Hierarchie gemäß diesen Regeln sortiert werden.
>>> E.__mro__
(
<class 'E'>,
<class 'C'>,
<class 'A'>,
<class 'D'>,
<class 'B'>,
<class 'object'>)
>>>
Der zugrunde liegende Algorithmus wird als "C3 Linearization Algorithm" bezeichnet. Die genauen Details sind nicht wichtig, solange Sie sich daran erinnern, dass eine Klassenhierarchie die gleichen Einordnungsregeln befolgt, denen Sie folgen würden, wenn Ihr Haus in Flammen steht und Sie evakuieren müssen - Kinder zuerst, gefolgt von Eltern.
Betrachten Sie zwei völlig unverbundene Objekte:
class Dog:
def noise(self):
return 'Bark'
def chase(self):
return 'Chasing!'
class LoudDog(Dog):
def noise(self):
## Code-Ähnlichkeit mit LoudBike (siehe unten)
return super().noise().upper()
Und
class Bike:
def noise(self):
return 'On Your Left'
def pedal(self):
return 'Pedaling!'
class LoudBike(Bike):
def noise(self):
## Code-Ähnlichkeit mit LoudDog (siehe oben)
return super().noise().upper()
Es gibt eine Code-Ähnlichkeit in der Implementierung von LoudDog.noise()
und LoudBike.noise()
. Tatsächlich ist der Code genau derselbe. Natürlich wird ein solcher Code sicherlich Softwareingenieure ansprechen.
Das Mixin-Muster ist eine Klasse mit einem Codefragment.
class Loud:
def noise(self):
return super().noise().upper()
Diese Klasse kann nicht in isolation verwendet werden. Sie mischt sich mit anderen Klassen über Vererbung.
class LoudDog(Loud, Dog):
pass
class LoudBike(Loud, Bike):
pass
Wunderbarerweise wurde die Lautstärke jetzt nur einmal implementiert und in zwei völlig unverbundenen Klassen wiederverwendet. Solch ein Trick ist eine der primären Anwendungen der Mehrfachvererbung in Python.
super()
?Verwenden Sie immer super()
beim Überschreiben von Methoden.
class Loud:
def noise(self):
return super().noise().upper()
super()
delegiert an die nächste Klasse in der MRO.
Das Knackige dabei ist, dass Sie nicht wissen, was es ist. Vor allem wissen Sie es nicht, wenn Mehrfachvererbung verwendet wird.
Mehrfachvererbung ist ein leistungsstarkes Werkzeug. Denken Sie daran: Mit Macht kommt auch Verantwortung. Frameworks / Bibliotheken verwenden es manchmal für fortgeschrittene Funktionen, die die Komposition von Komponenten betreffen. Vergessen Sie jetzt, dass Sie das gesehen haben.
Im Abschnitt 4 haben Sie eine Klasse Stock
definiert, die einen Aktienbesitz repräsentiert. In dieser Übung werden wir diese Klasse verwenden. Starten Sie den Interpreter neu und erstellen Sie einige Instanzen:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>>
Im interaktiven Shell untersuchen Sie die zugrunde liegenden Wörterbücher der beiden Instanzen, die Sie erstellt haben:
>>> goog.__dict__
... schauen Sie sich das Ergebnis an...
>>> ibm.__dict__
... schauen Sie sich das Ergebnis an...
>>>
Versuchen Sie, einem der obigen Instanzen ein neues Attribut zuzuweisen:
>>> goog.date = '6/11/2007'
>>> goog.__dict__
... schauen Sie sich das Ergebnis an...
>>> ibm.__dict__
... schauen Sie sich das Ergebnis an...
>>>
In der obigen Ausgabe werden Sie feststellen, dass die goog
-Instanz ein Attribut date
hat, während die ibm
-Instanz es nicht hat. Es ist wichtig zu beachten, dass Python eigentlich keine Beschränkungen für Attribute stellt. Beispielsweise sind die Attribute einer Instanz nicht auf die in der __init__()
-Methode eingerichteten beschränkt.
Anstatt ein Attribut zuzuweisen, versuchen Sie, einen neuen Wert direkt in das __dict__
-Objekt zu platzieren:
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>>
Hier erkennen Sie wirklich die Tatsache, dass eine Instanz einfach eine Schicht über einem Wörterbuch ist. Hinweis: Es sollte betont werden, dass das direkte Manipulieren des Wörterbuchs ungewöhnlich ist - Sie sollten immer Ihren Code so schreiben, dass Sie die (.)-Syntax verwenden.
Die Definitionen, die zu einer Klassendefinition gehören, werden von allen Instanzen dieser Klasse geteilt. Beachten Sie, dass alle Instanzen einen Link zurück zu ihrer zugehörigen Klasse haben:
>>> goog.__class__
... schauen Sie sich das Ergebnis an...
>>> ibm.__class__
... schauen Sie sich das Ergebnis an...
>>>
Versuchen Sie, eine Methode auf den Instanzen aufzurufen:
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
>>>
Beachten Sie, dass der Name 'cost' weder in goog.__dict__
noch in ibm.__dict__
definiert ist. Stattdessen wird er von dem Klassenwörterbuch bereitgestellt. Versuchen Sie das:
>>> Stock.__dict__['cost']
... schauen Sie sich das Ergebnis an...
>>>
Versuchen Sie, die cost()
-Methode direkt über das Wörterbuch aufzurufen:
>>> Stock.__dict__['cost'](goog)
49010.0
>>> Stock.__dict__['cost'](ibm)
4561.5
>>>
Beachten Sie, wie Sie die in der Klassendefinition definierte Funktion aufrufen und wie das self
-Argument die Instanz erhält.
Versuchen Sie, einem neuen Attribut der Stock
-Klasse hinzuzufügen:
>>> Stock.foo = 42
>>>
Beachten Sie, wie dieses neue Attribut jetzt auf allen Instanzen erscheint:
>>> goog.foo
42
>>> ibm.foo
42
>>>
Beachten Sie jedoch, dass es kein Teil des Instanzenwörterbuchs ist:
>>> goog.__dict__
... schauen Sie sich das Ergebnis an und bemerken Sie, dass es kein 'foo'-Attribut gibt...
>>>
Der Grund, warum Sie das foo
-Attribut auf Instanzen zugreifen können, ist, dass Python immer das Klassenwörterbuch überprüft, wenn es etwas auf der Instanz selbst nicht finden kann.
Hinweis: Dieser Teil der Übung veranschaulicht etwas, das als Klassenvariable bekannt ist. Nehmen Sie beispielsweise an, dass Sie eine Klasse wie diese haben:
class Foo(object):
a = 13 ## Klassenvariable
def __init__(self,b):
self.b = b ## Instanzvariable
In dieser Klasse ist die Variable a
, die im Körper der Klasse selbst zugewiesen wird, eine "Klassenvariable". Sie wird von allen erstellten Instanzen geteilt. Beispielsweise:
>>> f = Foo(10)
>>> g = Foo(20)
>>> f.a ## Überprüfen Sie die Klassenvariable (gleich für beide Instanzen)
13
>>> g.a
13
>>> f.b ## Überprüfen Sie die Instanzvariable (unterschiedlich)
10
>>> g.b
20
>>> Foo.a = 42 ## Ändern Sie den Wert der Klassenvariablen
>>> f.a
42
>>> g.a
42
>>>
Ein subtiler Aspekt von Python ist, dass das Aufrufen einer Methode tatsächlich zwei Schritte umfasst und etwas, das als gebundene Methode bekannt ist. Beispielsweise:
>>> s = goog.sell
>>> s
<bound method Stock.sell of Stock('GOOG', 100, 490.1)>
>>> s(25)
>>> goog.shares
75
>>>
Gebundene Methoden enthalten tatsächlich alle Bestandteile, die für das Aufrufen einer Methode erforderlich sind. Beispielsweise behalten sie einen Verweis auf die Funktion, die die Methode implementiert:
>>> s.__func__
<function sell at 0x10049af50>
>>>
Dies ist der gleiche Wert wie der, der im Stock
-Wörterbuch gefunden wird.
>>> Stock.__dict__['sell']
<function sell at 0x10049af50>
>>>
Gebundene Methoden verzeichnen auch die Instanz, die das self
-Argument ist.
>>> s.__self__
Stock('GOOG',75,490.1)
>>>
Wenn Sie die Funktion mit ()
aufrufen, kommen alle Bestandteile zusammen. Beispielsweise führt das Aufrufen von s(25)
tatsächlich folgendes aus:
>>> s.__func__(s.__self__, 25) ## Identisch mit s(25)
>>> goog.shares
50
>>>
Erstellen Sie eine neue Klasse, die von Stock
erbt.
>>> class NewStock(Stock):
def yow(self):
print('Yow!')
>>> n = NewStock('ACME', 50, 123.45)
>>> n.cost()
6172.50
>>> n.yow()
Yow!
>>>
Die Vererbung wird durch Erweiterung des Suchprozesses für Attribute implementiert. Das __bases__
-Attribut hat ein Tupel der unmittelbaren Elternklassen:
>>> NewStock.__bases__
(<class'stock.Stock'>,)
>>>
Das __mro__
-Attribut hat ein Tupel aller Elternklassen in der Reihenfolge, in der nach Attributen gesucht wird.
>>> NewStock.__mro__
(<class '__main__.NewStock'>, <class'stock.Stock'>, <class 'object'>)
>>>
So würde die cost()
-Methode der oben genannten Instanz n
gefunden werden:
>>> for cls in n.__class__.__mro__:
if 'cost' in cls.__dict__:
break
>>> cls
<class '__main__.Stock'>
>>> cls.__dict__['cost']
<function cost at 0x101aed598>
>>>
Herzlichen Glückwunsch! Sie haben das Lab "Dictionaries Revisited" abgeschlossen. Sie können in LabEx weitere Labs ausprobieren, um Ihre Fähigkeiten zu verbessern.