Einführung
In diesem Lab werden Sie lernen, wie Sie das Verhalten von Objekten durch die Neudefinition spezieller Methoden anpassen können. Sie werden auch die Art und Weise ändern, wie benutzerdefinierte Objekte ausgegeben werden, und Objekte vergleichbar machen.
Zusätzlich werden Sie lernen, einen Kontext-Manager (Context Manager) zu erstellen. Die Datei, die in diesem Lab bearbeitet werden soll, ist stock.py.
Verbessern der Objektrepräsentation mit __repr__
In Python können Objekte auf zwei verschiedene Arten als Zeichenketten (Strings) dargestellt werden. Diese Darstellungen dienen unterschiedlichen Zwecken und sind in verschiedenen Szenarien nützlich.
Der erste Typ ist die Zeichenkettenrepräsentation. Diese wird von der str()-Funktion erstellt, die automatisch aufgerufen wird, wenn Sie die print()-Funktion verwenden. Die Zeichenkettenrepräsentation ist für die menschliche Lesbarkeit konzipiert. Sie stellt das Objekt in einem Format dar, das für uns leicht zu verstehen und zu interpretieren ist.
Der zweite Typ ist die Code - Repräsentation. Diese wird von der repr()-Funktion generiert. Die Code - Repräsentation zeigt den Code, den Sie schreiben müssten, um das Objekt neu zu erstellen. Sie geht eher darum, eine präzise und eindeutige Möglichkeit zur Darstellung des Objekts im Code bereitzustellen.
Schauen wir uns ein konkretes Beispiel an, indem wir die eingebaute date-Klasse von Python verwenden. Dies wird Ihnen helfen, den Unterschied zwischen der Zeichenketten- und der Code - Repräsentation in der Praxis zu verstehen.
>>> from datetime import date
>>> d = date(2008, 7, 5)
>>> print(d) ## Uses str()
2008-07-05
>>> d ## Uses repr()
datetime.date(2008, 7, 5)
In diesem Beispiel ruft Python bei print(d) die str()-Funktion auf das date-Objekt d auf, und wir erhalten ein für Menschen lesbares Datum im Format YYYY-MM-DD. Wenn wir einfach d in der interaktiven Shell eingeben, ruft Python die repr()-Funktion auf, und wir sehen den Code, der benötigt wird, um das date-Objekt neu zu erstellen.
Sie können die repr()-Zeichenkette auf verschiedene Weise explizit abrufen. Hier sind einige Beispiele:
>>> print('The date is', repr(d))
The date is datetime.date(2008, 7, 5)
>>> print(f'The date is {d!r}')
The date is datetime.date(2008, 7, 5)
>>> print('The date is %r' % d)
The date is datetime.date(2008, 7, 5)
Jetzt wenden wir dieses Konzept auf unsere Stock-Klasse an. Wir werden die Klasse verbessern, indem wir die __repr__-Methode implementieren. Diese spezielle Methode wird von Python aufgerufen, wenn es die Code - Repräsentation eines Objekts benötigt.
Um dies zu tun, öffnen Sie die Datei stock.py in Ihrem Editor. Fügen Sie dann die __repr__-Methode zur Stock-Klasse hinzu. Die __repr__-Methode sollte eine Zeichenkette zurückgeben, die den Code zeigt, der benötigt wird, um das Stock-Objekt neu zu erstellen.
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
Nachdem Sie die __repr__-Methode hinzugefügt haben, sollte Ihre vollständige Stock-Klasse jetzt so aussehen:
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, shares):
self.shares -= shares
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
Jetzt testen wir unsere modifizierte Stock-Klasse. Öffnen Sie eine interaktive Python-Shell, indem Sie den folgenden Befehl in Ihrem Terminal ausführen:
python3
Sobald die interaktive Shell geöffnet ist, versuchen Sie die folgenden Befehle:
>>> import stock
>>> goog = stock.Stock('GOOG', 100, 490.10)
>>> goog
Stock('GOOG', 100, 490.1)
Sie können auch sehen, wie die __repr__-Methode mit einem Portfolio von Aktien funktioniert. Hier ist ein Beispiel:
>>> import reader
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> portfolio
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]
Wie Sie sehen können, hat die __repr__-Methode unsere Stock-Objekte viel informativer gemacht, wenn sie in der interaktiven Shell oder im Debugger angezeigt werden. Sie zeigt jetzt den Code, der benötigt wird, um jedes Objekt neu zu erstellen, was für das Debugging und das Verständnis des Zustands der Objekte sehr nützlich ist.
Wenn Sie mit dem Testen fertig sind, können Sie den Python-Interpreter beenden, indem Sie den folgenden Befehl ausführen:
>>> exit()
Vergleichbarkeit von Objekten mit __eq__
In Python ruft Python tatsächlich die spezielle Methode __eq__ auf, wenn Sie den ==-Operator verwenden, um zwei Objekte zu vergleichen. Standardmäßig vergleicht diese Methode die Identitäten der Objekte, was bedeutet, dass sie prüft, ob sie an der gleichen Speicheradresse gespeichert sind, anstatt ihren Inhalt zu vergleichen.
Schauen wir uns ein Beispiel an. Nehmen wir an, wir haben eine Stock-Klasse und erstellen zwei Stock-Objekte mit denselben Werten. Dann versuchen wir, sie mit dem ==-Operator zu vergleichen. So können Sie es in der Python-Shell tun:
Zuerst starten Sie die Python-Shell, indem Sie den folgenden Befehl in Ihrem Terminal ausführen:
python3
Dann führen Sie in der Python-Shell den folgenden Code aus:
>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
False
Wie Sie sehen können, hält Python die beiden Stock-Objekte a und b für unterschiedliche Objekte, obwohl sie die gleichen Werte für ihre Attribute (name, shares und price) haben, weil sie an verschiedenen Speicherorten gespeichert sind.
Um dieses Problem zu beheben, können wir die __eq__-Methode in unserer Stock-Klasse implementieren. Diese Methode wird jedes Mal aufgerufen, wenn der ==-Operator auf Objekte der Stock-Klasse angewendet wird.
Öffnen Sie jetzt erneut die Datei stock.py. Fügen Sie innerhalb der Stock-Klasse die folgende __eq__-Methode hinzu:
def __eq__(self, other):
return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
(other.name, other.shares, other.price))
Lassen Sie uns analysieren, was diese Methode tut:
- Zuerst verwendet sie die
isinstance-Funktion, um zu prüfen, ob dasother-Objekt eine Instanz derStock-Klasse ist. Dies ist wichtig, weil wir nurStock-Objekte mit anderenStock-Objekten vergleichen möchten. - Wenn
othereinStock-Objekt ist, vergleicht sie dann die Attribute (name,sharesundprice) sowohl desself-Objekts als auch desother-Objekts. - Sie gibt nur
Truezurück, wenn beide ObjekteStock-Instanzen sind und ihre Attribute identisch sind.
Nachdem Sie die __eq__-Methode hinzugefügt haben, sollte Ihre vollständige Stock-Klasse so aussehen:
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, shares):
self.shares -= shares
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
def __eq__(self, other):
return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
(other.name, other.shares, other.price))
Jetzt testen wir unsere verbesserte Stock-Klasse. Starten Sie die Python-Shell erneut:
python3
Führen Sie dann den folgenden Code in der Python-Shell aus:
>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
True
>>> c = stock.Stock('GOOG', 200, 490.1)
>>> a == c
False
Toll! Jetzt können unsere Stock-Objekte richtig anhand ihres Inhalts und nicht ihrer Speicheradressen verglichen werden.
Die isinstance-Prüfung in der __eq__-Methode ist von entscheidender Bedeutung. Sie stellt sicher, dass wir nur Stock-Objekte vergleichen. Wenn wir diese Prüfung nicht hätten, könnte das Vergleichen eines Stock-Objekts mit etwas, das kein Stock-Objekt ist, Fehler verursachen.
Wenn Sie mit dem Testen fertig sind, können Sie die Python-Shell beenden, indem Sie den folgenden Befehl ausführen:
>>> exit()
Erstellen eines Kontext-Managers
Ein Kontext-Manager ist ein spezieller Objekttyp in Python. In Python können Objekte verschiedene Methoden haben, die ihr Verhalten definieren. Ein Kontext-Manager definiert speziell zwei wichtige Methoden: __enter__ und __exit__. Diese Methoden arbeiten zusammen mit der with-Anweisung. Die with-Anweisung wird verwendet, um einen bestimmten Kontext für einen Codeblock einzurichten. Stellen Sie sich das wie die Erstellung einer kleinen Umgebung vor, in der bestimmte Dinge passieren, und wenn der Codeblock beendet ist, kümmert sich der Kontext-Manager um die Aufräumarbeiten.
In diesem Schritt werden wir einen Kontext-Manager erstellen, der eine sehr nützliche Funktion hat. Er wird die Standardausgabe (sys.stdout) temporär umleiten. Die Standardausgabe ist der Ort, an den die normale Ausgabe Ihres Python-Programms geht, normalerweise die Konsole. Indem wir sie umleiten, können wir die Ausgabe stattdessen in eine Datei senden. Dies ist praktisch, wenn Sie die Ausgabe speichern möchten, die sonst nur auf der Konsole angezeigt würde.
Zuerst müssen wir eine neue Datei erstellen, um unseren Kontext-Manager-Code zu schreiben. Wir werden diese Datei redirect.py nennen. Sie können sie mit dem folgenden Befehl im Terminal erstellen:
touch /home/labex/project/redirect.py
Jetzt, da die Datei erstellt ist, öffnen Sie sie in einem Editor. Sobald sie geöffnet ist, fügen Sie den folgenden Python-Code zur Datei hinzu:
import sys
class redirect_stdout:
def __init__(self, out_file):
self.out_file = out_file
def __enter__(self):
self.stdout = sys.stdout
sys.stdout = self.out_file
return self.out_file
def __exit__(self, ty, val, tb):
sys.stdout = self.stdout
Lassen Sie uns analysieren, was dieser Kontext-Manager tut:
__init__: Dies ist die Initialisierungsmethode. Wenn wir eine Instanz derredirect_stdout-Klasse erstellen, übergeben wir ein Dateiobjekt. Diese Methode speichert dieses Dateiobjekt in der Instanzvariablenself.out_file. So merkt sie sich, wohin wir die Ausgabe umleiten möchten.__enter__:- Zuerst speichert es die aktuelle
sys.stdout. Dies ist wichtig, weil wir sie später wiederherstellen müssen. - Dann ersetzt es die aktuelle
sys.stdoutdurch unser Dateiobjekt. Ab diesem Zeitpunkt geht jede Ausgabe, die normalerweise an die Konsole gehen würde, stattdessen in die Datei. - Schließlich gibt es das Dateiobjekt zurück. Dies ist nützlich, weil wir möglicherweise das Dateiobjekt innerhalb des
with-Blocks verwenden möchten.
- Zuerst speichert es die aktuelle
__exit__:- Diese Methode stellt die ursprüngliche
sys.stdoutwieder her. So geht die Ausgabe nach Abschluss deswith-Blocks wieder normalerweise an die Konsole. - Sie nimmt drei Parameter entgegen: Ausnahmetyp (
ty), Ausnahme-Wert (val) und Stapelüberwachung (tb). Diese Parameter sind für das Kontext-Manager-Protokoll erforderlich. Sie werden verwendet, um alle Ausnahmen zu behandeln, die innerhalb deswith-Blocks auftreten können.
- Diese Methode stellt die ursprüngliche
Jetzt testen wir unseren Kontext-Manager. Wir werden ihn verwenden, um die Ausgabe einer Tabelle in eine Datei umzuleiten. Zuerst starten Sie die Python-Shell:
python3
Führen Sie dann den folgenden Python-Code in der Shell aus:
>>> import stock, reader, tableformat
>>> from redirect import redirect_stdout
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> formatter = tableformat.create_formatter('text')
>>> with redirect_stdout(open('out.txt', 'w')) as file:
... tableformat.print_table(portfolio, ['name','shares','price'], formatter)
... file.close()
...
>>> ## Let's check the content of the output file
>>> print(open('out.txt').read())
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
Toll! Unser Kontext-Manager hat wie erwartet funktioniert. Er hat die Tabellenausgabe erfolgreich in die Datei out.txt umgeleitet.
Kontext-Manager sind eine sehr mächtige Funktion in Python. Sie helfen Ihnen, Ressourcen richtig zu verwalten. Hier sind einige häufige Anwendungsfälle für Kontext-Manager:
- Dateioperationen: Wenn Sie eine Datei öffnen, kann ein Kontext-Manager sicherstellen, dass die Datei ordnungsgemäß geschlossen wird, auch wenn ein Fehler auftritt.
- Datenbankverbindungen: Er kann sicherstellen, dass die Datenbankverbindung geschlossen wird, nachdem Sie sie verwendet haben.
- Sperren in Thread-Programmen: Kontext-Manager können Ressourcen auf sichere Weise sperren und entsperren.
- Temporäres Ändern von Umgebungs-Einstellungen: Sie können einige Einstellungen für einen Codeblock ändern und sie dann automatisch wiederherstellen.
Dieses Muster ist sehr wichtig, weil es sicherstellt, dass Ressourcen ordnungsgemäß aufgeräumt werden, auch wenn eine Ausnahme innerhalb des with-Blocks auftritt.
Wenn Sie mit dem Testen fertig sind, können Sie die Python-Shell beenden:
>>> exit()
Zusammenfassung
In diesem Lab haben Sie gelernt, wie Sie die String - Darstellung von Objekten mit der __repr__ - Methode anpassen, Objekte mit der __eq__ - Methode vergleichbar machen und einen Kontext - Manager mit den __enter__ - und __exit__ - Methoden erstellen können. Diese speziellen "Dunder - Methoden" sind der Grundstein für die objektorientierten Features von Python.
Das Implementieren dieser Methoden in Ihren Klassen ermöglicht es Ihren Objekten, sich wie eingebaute Typen zu verhalten und nahtlos in die Sprachfeatures von Python zu integrieren. Spezielle Methoden ermöglichen verschiedene Funktionen wie benutzerdefinierte String - Darstellungen, Objektvergleiche und Kontextverwaltung. Wenn Sie sich in Python weiterentwickeln, werden Sie noch mehr spezielle Methoden entdecken, um das leistungsstarke Objektmodell zu nutzen.