Einführung
Vererbung ist ein häufig verwendetes Werkzeug zum Schreiben von erweiterbaren Programmen. In diesem Abschnitt wird diese Idee untersucht.
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
Vererbung ist ein häufig verwendetes Werkzeug zum Schreiben von erweiterbaren Programmen. In diesem Abschnitt wird diese Idee untersucht.
Vererbung wird verwendet, um vorhandene Objekte zu spezialisieren:
class Parent:
...
class Child(Parent):
...
Die neue Klasse Child
wird als abgeleitete Klasse oder Unterklasse bezeichnet. Die Parent
-Klasse ist als Basisklasse oder Superklasse bekannt. Parent
wird in ()
nach dem Klassennamen angegeben, class Child(Parent):
.
Mit Vererbung übernimmst du eine vorhandene Klasse und:
Am Ende erweitern Sie vorhandenen Code.
Angenommen, das ist deine Ausgangsklasse:
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
Du kannst jeder Teil davon über Vererbung ändern.
class MyStock(Stock):
def panic(self):
self.sell(self.shares)
Verwendungsbeispiel.
>>> s = MyStock('GOOG', 100, 490.1)
>>> s.sell(25)
>>> s.shares
75
>>> s.panic()
>>> s.shares
0
>>>
class MyStock(Stock):
def cost(self):
return 1.25 * self.shares * self.price
Verwendungsbeispiel.
>>> s = MyStock('GOOG', 100, 490.1)
>>> s.cost()
61262.5
>>>
Die neue Methode ersetzt die alte. Die anderen Methoden bleiben unberührt. Es ist großartig.
Manchmal erweitert eine Klasse eine vorhandene Methode, aber möchte die ursprüngliche Implementierung innerhalb der Neudefinition verwenden. Dazu nutzt man super()
:
class Stock:
...
def cost(self):
return self.shares * self.price
...
class MyStock(Stock):
def cost(self):
## Überprüfe den Aufruf von `super`
actual_cost = super().cost()
return 1.25 * actual_cost
Verwende super()
, um die vorherige Version aufzurufen.
Hinweis: In Python 2 war die Syntax umständlicher.
actual_cost = super(MyStock, self).cost()
__init__
und VererbungWenn __init__
neu definiert wird, ist es unerlässlich, die Elternklasse zu initialisieren.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
class MyStock(Stock):
def __init__(self, name, shares, price, factor):
## Überprüfe den Aufruf von `super` und `__init__`
super().__init__(name, shares, price)
self.factor = factor
def cost(self):
return self.factor * super().cost()
Du solltest die __init__()
-Methode auf dem super
aufrufen, was der Weg ist, um die vorherige Version aufzurufen, wie zuvor gezeigt.
Vererbung wird manchmal verwendet, um verwandte Objekte zu organisieren.
class Shape:
...
class Circle(Shape):
...
class Rectangle(Shape):
...
Denke an eine logische Hierarchie oder Taxonomie. Ein häufiger (und praktischer) Einsatz ist jedoch in Bezug auf wiederverwendbaren oder erweiterbaren Code. Beispielsweise kann ein Framework eine Basisklasse definieren und Sie auffordern, sie anzupassen.
class CustomHandler(TCPHandler):
def handle_request(self):
...
## Anpassende Verarbeitung
Die Basisklasse enthält einige allgemeingültigen Code. Ihre Klasse erbt und passt spezifische Teile an.
Die Vererbung etabliert eine Typbeziehung.
class Shape:
...
class Circle(Shape):
...
Prüfe auf Objektinstanz.
>>> c = Circle(4.0)
>>> isinstance(c, Shape)
True
>>>
Wichtig: Idealerweise sollte jeder Code, der mit Instanzen der Elternklasse funktioniert, auch mit Instanzen der Kindklasse funktionieren.
object
Wenn eine Klasse keine Elternklasse hat, sieht man manchmal object
als Basis verwendet.
class Shape(object):
...
object
ist die Elternklasse aller Objekte in Python.
*Hinweis: Technisch gesehen ist dies nicht erforderlich, aber man sieht es oft als Überbleibsel aus der erforderlichen Verwendung in Python 2 angegeben. Wenn es weggelassen wird, erbt die Klasse immer noch implizit von object
.
Du kannst von mehreren Klassen erben, indem du sie in der Klassendefinition angibst.
class Mother:
...
class Father:
...
class Child(Mother, Father):
...
Die Klasse Child
erbt Eigenschaften von beiden Elternklassen. Es gibt einige recht knifflige Details. Mach es nicht, es sei denn, du weißt, was du tust. In der nächsten Abschnitt werden einige weitere Informationen gegeben, aber wir werden in diesem Kurs nicht weiter auf Mehrfachvererbung zurückgreifen.
Ein wichtiger Einsatz von Vererbung besteht darin, Code zu schreiben, der auf verschiedene Weise erweitert oder angepasst werden soll - insbesondere in Bibliotheken oder Frameworks. Um dies zu veranschaulichen, betrachte die Funktion print_report()
in deinem Programm report.py
. Sie sollte ungefähr so aussehen:
def print_report(reportdata):
'''
Druckt eine schön formattierte Tabelle aus einer Liste von (Name, Anteile, Preis, Änderung) - Tupeln.
'''
headers = ('Name','Anteile','Preis','Änderung')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 +' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
Wenn du dein Report-Programm ausführst, solltest du eine Ausgabe wie diese erhalten:
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
Angenommen, Sie möchten die Funktion print_report()
so ändern, dass sie verschiedene Ausgabeformate wie einfachen Text, HTML, CSV oder XML unterstützt. Um dies zu tun, könnten Sie versuchen, eine riesige Funktion zu schreiben, die alles macht. Dies würde jedoch wahrscheinlich zu einem unhaltbaren Durcheinander führen. Stattdessen ist dies eine perfekte Gelegenheit, die Vererbung zu verwenden.
Beginnen Sie mit den Schritten, die bei der Erstellung einer Tabelle beteiligt sind. Am Anfang der Tabelle befindet sich eine Reihe von Tabellenüberschriften. Danach erscheinen die Zeilen der Tabellendaten. Nehmen wir diese Schritte und bringen Sie sie in ihre eigene Klasse. Erstellen Sie eine Datei namens tableformat.py
und definieren Sie die folgende Klasse:
## tableformat.py
class TableFormatter:
def headings(self, headers):
'''
Gibt die Tabellenüberschriften aus.
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Gibt eine einzelne Zeile der Tabellendaten aus.
'''
raise NotImplementedError()
Diese Klasse tut nichts, aber sie dient als Art von Entwurfspezifikation für zusätzliche Klassen, die bald definiert werden. Eine Klasse wie diese wird manchmal als "abstrakte Basisklasse" bezeichnet.
Ändern Sie die Funktion print_report()
, so dass sie ein TableFormatter
-Objekt als Eingabe akzeptiert und auf diesem Methoden aufruft, um die Ausgabe zu erzeugen. Beispielsweise wie folgt:
## report.py
...
def print_report(reportdata, formatter):
'''
Druckt eine schön formattierte Tabelle aus einer Liste von (Name, Anteile, Preis, Änderung) - Tupeln.
'''
formatter.headings(['Name','Anteile','Preis','Änderung'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
Da Sie einem Argument in print_report()
hinzugefügt haben, müssen Sie auch die Funktion portfolio_report()
ändern. Ändern Sie sie so, dass sie einen TableFormatter
wie folgt erstellt:
## report.py
import tableformat
...
def portfolio_report(portfoliofile, pricefile):
'''
Erstellt einen Aktienbericht aus den Portfolio - und Preisdaten - Dateien.
'''
## Lese die Daten - Dateien
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Erstelle die Berichts - Daten
report = make_report_data(portfolio, prices)
## Drucke sie aus
formatter = tableformat.TableFormatter()
print_report(report, formatter)
Führen Sie diesen neuen Code aus:
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
... stürzt ab...
Es sollte sofort mit einer NotImplementedError
-Ausnahme abstürzen. Das ist nicht sehr aufregend, aber genau das, was wir erwartet haben. Fortfahren Sie mit dem nächsten Teil.
Die Klasse TableFormatter
, die Sie im Teil (a) definiert haben, soll über die Vererbung erweitert werden. Tatsächlich ist das der ganze Gedanke. Um dies zu veranschaulichen, definieren Sie eine Klasse TextTableFormatter
wie folgt:
## tableformat.py
...
class TextTableFormatter(TableFormatter):
'''
Gibt eine Tabelle im einfachen - Text - Format aus
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 +' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
Ändern Sie die Funktion portfolio_report()
wie folgt und testen Sie sie:
## report.py
...
def portfolio_report(portfoliofile, pricefile):
'''
Erstellt einen Aktienbericht aus den Portfolio - und Preisdaten - Dateien.
'''
## Lese die Daten - Dateien
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Erstelle die Berichts - Daten
report = make_report_data(portfolio, prices)
## Drucke sie aus
formatter = tableformat.TextTableFormatter()
print_report(report, formatter)
Dies sollte die gleiche Ausgabe wie zuvor erzeugen:
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
>>>
Ändern wir jedoch die Ausgabe in etwas anderes. Definieren Sie eine neue Klasse CSVTableFormatter
, die die Ausgabe im CSV - Format erzeugt:
## tableformat.py
...
class CSVTableFormatter(TableFormatter):
'''
Gibt Portfolio - Daten im CSV - Format aus.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
Ändern Sie Ihr Hauptprogramm wie folgt:
def portfolio_report(portfoliofile, pricefile):
'''
Erstellt einen Aktienbericht aus den Portfolio - und Preisdaten - Dateien.
'''
## Lese die Daten - Dateien
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Erstelle die Berichts - Daten
report = make_report_data(portfolio, prices)
## Drucke sie aus
formatter = tableformat.CSVTableFormatter()
print_report(report, formatter)
Sie sollten jetzt die folgende CSV - Ausgabe sehen:
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
Name,Shares,Price,Change
AA,100,9.22,-22.98
IBM,50,106.28,15.18
CAT,150,35.46,-47.98
MSFT,200,20.89,-30.34
GE,95,13.48,-26.89
MSFT,50,20.89,-44.21
IBM,100,106.28,35.84
Mit einem ähnlichen Ansatz definieren Sie eine Klasse HTMLTableFormatter
, die eine Tabelle mit der folgenden Ausgabe erzeugt:
<tr><th>Name</th><th>Shares</th><th>Price</th><th>Change</th></tr>
<tr><td>AA</td><td>100</td><td>9.22</td><td>-22.98</td></tr>
<tr><td>IBM</td><td>50</td><td>106.28</td><td>15.18</td></tr>
<tr><td>CAT</td><td>150</td><td>35.46</td><td>-47.98</td></tr>
<tr><td>MSFT</td><td>200</td><td>20.89</td><td>-30.34</td></tr>
<tr><td>GE</td><td>95</td><td>13.48</td><td>-26.89</td></tr>
<tr><td>MSFT</td><td>50</td><td>20.89</td><td>-44.21</td></tr>
<tr><td>IBM</td><td>100</td><td>106.28</td><td>35.84</td></tr>
Testen Sie Ihren Code, indem Sie das Hauptprogramm ändern, um ein HTMLTableFormatter
-Objekt statt eines CSVTableFormatter
-Objekts zu erstellen.
Ein wichtiges Merkmal der objektorientierten Programmierung ist, dass Sie ein Objekt in ein Programm einfügen können und es funktioniert, ohne dass Sie den vorhandenen Code ändern müssen. Beispielsweise würde ein Programm, das erwartet, ein TableFormatter
-Objekt zu verwenden, funktionieren, unabhängig davon, welchem TableFormatter
Sie es tatsächlich geben. Dieses Verhalten wird manchmal als "Polymorphismus" bezeichnet.
Ein potenzielles Problem besteht darin, herauszufinden, wie es möglich ist, dass ein Benutzer den Formatter auswählt, den er möchte. Das direkte Verwenden von Klassennamen wie TextTableFormatter
ist oft störend. Daher könnten Sie einen vereinfachten Ansatz in Betracht ziehen. Vielleicht integrieren Sie eine if
-Anweisung in den Code wie folgt:
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Erstellt einen Aktienbericht aus den Portfolio - und Preisdaten - Dateien.
'''
## Lese die Daten - Dateien
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Erstelle die Berichts - Daten
report = make_report_data(portfolio, prices)
## Drucke sie aus
if fmt == 'txt':
formatter = tableformat.TextTableFormatter()
elif fmt == 'csv':
formatter = tableformat.CSVTableFormatter()
elif fmt == 'html':
formatter = tableformat.HTMLTableFormatter()
else:
raise RuntimeError(f'Unknown format {fmt}')
print_report(report, formatter)
In diesem Code gibt der Benutzer einen vereinfachten Namen wie 'txt'
oder 'csv'
an, um ein Format auszuwählen. Ist es jedoch die beste Idee, eine große if
-Anweisung in der Funktion portfolio_report()
so zu platzieren? Es wäre vielleicht besser, diesen Code an eine allgemeine Funktion an einem anderen Ort zu verschieben.
In der Datei tableformat.py
fügen Sie eine Funktion create_formatter(name)
hinzu, die es einem Benutzer ermöglicht, einen Formatter anhand eines Ausgabennamens wie 'txt'
, 'csv'
oder 'html'
zu erstellen. Ändern Sie portfolio_report()
so, dass es wie folgt aussieht:
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Erstellt einen Aktienbericht aus den Portfolio - und Preisdaten - Dateien.
'''
## Lese die Daten - Dateien
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Erstelle die Berichts - Daten
report = make_report_data(portfolio, prices)
## Drucke sie aus
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
Testen Sie die Funktion mit verschiedenen Formaten, um sicherzustellen, dass sie funktioniert.
Ändern Sie das Programm report.py
so, dass die Funktion portfolio_report()
ein optionales Argument erhält, das das Ausgabeformat angibt. Beispielsweise:
>>> report.portfolio_report('portfolio.csv', 'prices.csv', 'txt')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
>>>
Ändern Sie das Hauptprogramm so, dass ein Format über die Befehlszeile angegeben werden kann:
$ python3 report.py portfolio.csv prices.csv csv
Name,Shares,Price,Change
AA,100,9.22,-22.98
IBM,50,106.28,15.18
CAT,150,35.46,-47.98
MSFT,200,20.89,-30.34
GE,95,13.48,-26.89
MSFT,50,20.89,-44.21
IBM,100,106.28,35.84
$
Das Schreiben von erweiterbarem Code ist eine der häufigsten Anwendungen der Vererbung in Bibliotheken und Frameworks. Beispielsweise kann ein Framework Sie dazu anweisen, Ihr eigenes Objekt zu definieren, das von einer bereitgestellten Basisklasse erbt. Anschließend werden Sie aufgefordert, verschiedene Methoden auszufüllen, die verschiedene Funktionalität implementieren.
Ein etwas tiefer liegender Begriff ist die Idee des "Besitzes Ihrer Abstraktionen". In den Übungen haben wir unsere eigene Klasse für die Formatierung einer Tabelle definiert. Sie können sich an Ihren Code sehen und sich sagen: "Ich sollte einfach eine Formatierungsbibliothek oder etwas anderes verwenden, das jemand anderer bereits gemacht hat!" Nein, Sie sollten sowohl Ihre Klasse als auch eine Bibliothek verwenden. Die Verwendung Ihrer eigenen Klasse fördert die lose Kopplung und ist flexibler. Solange Ihre Anwendung die Programmierschnittstelle Ihrer Klasse verwendet, können Sie die interne Implementierung auf jede beliebige Weise ändern, die Sie möchten. Sie können ausschließlich eigenes Code schreiben. Sie können eine Drittanbieter - Paket verwenden. Wenn Sie eines finden, können Sie ein Drittanbieter - Paket gegen ein anderes austauschen. Es spielt keine Rolle - Ihr Anwendungs - Code bricht nicht, solange Sie die Schnittstelle beibehalten. Das ist eine mächtige Idee und einer der Gründe, warum Sie für etwas wie dies die Vererbung in Betracht ziehen sollten.
Allerdings kann das Entwerfen objektorientierter Programme extrem schwierig sein. Für weitere Informationen sollten Sie wahrscheinlich Bücher zum Thema Design Patterns suchen (obwohl das Verständnis dessen, was in dieser Übung passiert ist, Sie weit bringt, was die praktische Verwendung von Objekten betrifft).
Herzlichen Glückwunsch! Sie haben das Vererbungslabor abgeschlossen. Sie können in LabEx weitere Labs ausprobieren, um Ihre Fähigkeiten zu verbessern.