Einführung
In diesem Lab lernen Sie die Konventionen für die Übergabe von Argumenten in Python-Funktionen kennen. Sie werden auch eine wiederverwendbare Struktur für Datenklassen erstellen und objektorientierte Entwurfsprinzipien anwenden, um Ihren Code zu vereinfachen.
Dieser Übungszweck besteht darin, die Datei stock.py auf eine organisiertere Weise neu zu schreiben. Bevor Sie beginnen, kopieren Sie Ihre bestehende Arbeit in der Datei stock.py in eine neue Datei namens orig_stock.py zur Referenz. Die Dateien, die Sie erstellen werden, sind structure.py und stock.py.
Verständnis der Übergabe von Funktionsargumenten
In Python sind Funktionen ein grundlegendes Konzept, das es Ihnen ermöglicht, eine Reihe von Anweisungen zusammenzufassen, um eine bestimmte Aufgabe auszuführen. Wenn Sie eine Funktion aufrufen, müssen Sie ihr oft einige Daten zur Verfügung stellen, die wir Argumente nennen. Python bietet verschiedene Möglichkeiten, diese Argumente an Funktionen zu übergeben. Diese Flexibilität ist unglaublich nützlich, da sie Ihnen hilft, saubereren und wartbareren Code zu schreiben. Bevor wir diese Techniken auf unser Projekt anwenden, schauen wir uns diese Argumentübergabekonventionen genauer an.
Erstellen einer Sicherungskopie Ihrer Arbeit
Bevor wir mit den Änderungen an unserer stock.py-Datei beginnen, ist es eine gute Praxis, eine Sicherungskopie zu erstellen. Auf diese Weise können wir immer zurück zur ursprünglichen Version gehen, wenn etwas während unserer Experimente schief geht. Um eine Sicherungskopie zu erstellen, öffnen Sie ein Terminal und führen Sie den folgenden Befehl aus:
cp stock.py orig_stock.py
Dieser Befehl verwendet den cp (Kopieren)-Befehl im Terminal. Er nimmt die stock.py-Datei und erstellt eine Kopie davon mit dem Namen orig_stock.py. Auf diese Weise stellen wir sicher, dass unsere ursprüngliche Arbeit sicher aufbewahrt wird.
Erkundung der Übergabe von Funktionsargumenten
In Python gibt es mehrere Möglichkeiten, Funktionen mit verschiedenen Argumenttypen aufzurufen. Lassen Sie uns jede dieser Methoden im Detail untersuchen.
1. Positionsabhängige Argumente
Die einfachste Art, Argumente an eine Funktion zu übergeben, ist die positionsabhängige Übergabe. Wenn Sie eine Funktion definieren, geben Sie eine Liste von Parametern an. Wenn Sie die Funktion aufrufen, stellen Sie Werte für diese Parameter in der gleichen Reihenfolge bereit, wie sie definiert sind.
Hier ist ein Beispiel:
def calculate(x, y, z):
return x + y + z
## Call with positional arguments
result = calculate(1, 2, 3)
print(result) ## Output: 6
In diesem Beispiel nimmt die calculate-Funktion drei Parameter entgegen: x, y und z. Wenn wir die Funktion mit calculate(1, 2, 3) aufrufen, wird der Wert 1 an x zugewiesen, 2 an y und 3 an z. Die Funktion addiert dann diese Werte und gibt das Ergebnis zurück.
2. Schlüsselwortargumente
Zusätzlich zu positionsabhängigen Argumenten können Sie auch Argumente anhand ihrer Namen angeben. Dies wird die Verwendung von Schlüsselwortargumenten genannt. Wenn Sie Schlüsselwortargumente verwenden, müssen Sie sich nicht um die Reihenfolge der Argumente kümmern.
Hier ist ein Beispiel:
## Call with a mix of positional and keyword arguments
result = calculate(1, z=3, y=2)
print(result) ## Output: 6
In diesem Beispiel übergeben wir zunächst das positionsabhängige Argument 1 für x. Dann verwenden wir Schlüsselwortargumente, um die Werte für y und z anzugeben. Die Reihenfolge der Schlüsselwortargumente spielt keine Rolle, solange Sie die richtigen Namen angeben.
3. Entpacken von Sequenzen und Wörterbüchern
Python bietet eine bequeme Möglichkeit, Sequenzen und Wörterbücher als Argumente zu übergeben, indem Sie die *- und **-Syntax verwenden. Dies wird Entpacken genannt.
Hier ist ein Beispiel für das Entpacken eines Tupels in positionsabhängige Argumente:
## Unpacking a tuple into positional arguments
args = (1, 2, 3)
result = calculate(*args)
print(result) ## Output: 6
In diesem Beispiel haben wir ein Tupel args, das die Werte 1, 2 und 3 enthält. Wenn wir den *-Operator vor args im Funktionsaufruf verwenden, entpackt Python das Tupel und übergibt seine Elemente als positionsabhängige Argumente an die calculate-Funktion.
Hier ist ein Beispiel für das Entpacken eines Wörterbuchs in Schlüsselwortargumente:
## Unpacking a dictionary into keyword arguments
kwargs = {'y': 2, 'z': 3}
result = calculate(1, **kwargs)
print(result) ## Output: 6
In diesem Beispiel haben wir ein Wörterbuch kwargs, das die Schlüssel-Wert-Paare 'y': 2 und 'z': 3 enthält. Wenn wir den **-Operator vor kwargs im Funktionsaufruf verwenden, entpackt Python das Wörterbuch und übergibt seine Schlüssel-Wert-Paare als Schlüsselwortargumente an die calculate-Funktion.
4. Akzeptieren von variablen Argumenten
Manchmal möchten Sie möglicherweise eine Funktion definieren, die eine beliebige Anzahl von Argumenten akzeptieren kann. Python ermöglicht Ihnen dies, indem Sie die *- und **-Syntax in der Funktionsdefinition verwenden.
Hier ist ein Beispiel für eine Funktion, die eine beliebige Anzahl von positionsabhängigen Argumenten akzeptiert:
## Accept any number of positional arguments
def sum_all(*args):
return sum(args)
print(sum_all(1, 2)) ## Output: 3
print(sum_all(1, 2, 3, 4, 5)) ## Output: 15
In diesem Beispiel verwendet die sum_all-Funktion den Parameter *args, um eine beliebige Anzahl von positionsabhängigen Argumenten zu akzeptieren. Der *-Operator sammelt alle positionsabhängigen Argumente in einem Tupel namens args. Die Funktion verwendet dann die integrierte sum-Funktion, um alle Elemente im Tupel aufzusummieren.
Hier ist ein Beispiel für eine Funktion, die eine beliebige Anzahl von Schlüsselwortargumenten akzeptiert:
## Accept any number of keyword arguments
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Python", year=1991)
## Output:
## name: Python
## year: 1991
In diesem Beispiel verwendet die print_info-Funktion den Parameter **kwargs, um eine beliebige Anzahl von Schlüsselwortargumenten zu akzeptieren. Der **-Operator sammelt alle Schlüsselwortargumente in einem Wörterbuch namens kwargs. Die Funktion iteriert dann über die Schlüssel-Wert-Paare im Wörterbuch und gibt sie aus.
Diese Techniken werden uns helfen, flexiblere und wiederverwendbare Code-Strukturen in den folgenden Schritten zu erstellen. Um uns mit diesen Konzepten vertrauter zu machen, öffnen wir den Python-Interpreter und probieren einige dieser Beispiele aus.
python3
Sobald Sie sich im Python-Interpreter befinden, versuchen Sie, die obigen Beispiele einzugeben. Dies gibt Ihnen praktische Erfahrung mit diesen Argumentübergabetechniken.
Erstellen einer Basisklasse für Strukturen
Nachdem wir nun ein gutes Verständnis der Übergabe von Funktionsargumenten haben, werden wir eine wiederverwendbare Basisklasse für Datenstrukturen erstellen. Dieser Schritt ist von entscheidender Bedeutung, da er uns hilft, den gleichen Code nicht immer wieder zu schreiben, wenn wir einfache Klassen erstellen, die Daten speichern. Durch die Verwendung einer Basisklasse können wir unseren Code vereinfachen und effizienter gestalten.
Das Problem mit wiederholendem Code
In den früheren Übungen haben Sie eine Stock-Klasse wie folgt definiert:
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Schauen Sie sich die __init__-Methode genau an. Sie werden feststellen, dass sie ziemlich wiederholend ist. Sie müssen jedes Attribut manuell einzeln zuweisen. Dies kann sehr mühsam und zeitaufwendig werden, insbesondere wenn Sie viele Klassen mit einer großen Anzahl von Attributen haben.
Erstellen einer flexiblen Basisklasse
Lassen Sie uns eine Structure-Basisklasse erstellen, die die Attributzuweisung automatisch handhaben kann. Zunächst öffnen Sie die WebIDE und erstellen Sie eine neue Datei mit dem Namen structure.py. Fügen Sie dann den folgenden Code in diese Datei ein:
## structure.py
class Structure:
"""
A base class for creating simple data structures.
Automatically populates object attributes from _fields and constructor arguments.
"""
_fields = ()
def __init__(self, *args):
## Check that the number of arguments matches the number of fields
if len(args) != len(self._fields):
raise TypeError(f"Expected {len(self._fields)} arguments")
## Set the attributes
for name, value in zip(self._fields, args):
setattr(self, name, value)
Diese Basisklasse hat mehrere wichtige Merkmale:
- Sie definiert eine Klassenvariable
_fields. Standardmäßig ist diese Variable leer. Diese Variable wird die Namen der Attribute enthalten, die die Klasse haben wird. - Sie prüft, ob die Anzahl der an den Konstruktor übergebenen Argumente mit der Anzahl der in
_fieldsdefinierten Felder übereinstimmt. Wenn sie nicht übereinstimmen, wird einTypeErrorausgelöst. Dies hilft uns, Fehler frühzeitig zu erkennen. - Sie setzt die Attribute des Objekts mithilfe der Feldnamen und der als Argumente bereitgestellten Werte. Die
setattr-Funktion wird verwendet, um die Attribute dynamisch zu setzen.
Testen unserer Structure-Basisklasse
Jetzt erstellen wir einige Beispielklassen, die von der Structure-Basisklasse erben. Fügen Sie den folgenden Code in Ihre structure.py-Datei ein:
## Example classes using Structure
class Stock(Structure):
_fields = ('name', 'shares', 'price')
class Point(Structure):
_fields = ('x', 'y')
class Date(Structure):
_fields = ('year', 'month', 'day')
Um zu testen, ob unsere Implementierung korrekt funktioniert, erstellen wir eine Testdatei mit dem Namen test_structure.py. Fügen Sie den folgenden Code in diese Datei ein:
## test_structure.py
from structure import Stock, Point, Date
## Test Stock class
s = Stock('GOOG', 100, 490.1)
print(f"Stock name: {s.name}, shares: {s.shares}, price: {s.price}")
## Test Point class
p = Point(3, 4)
print(f"Point coordinates: ({p.x}, {p.y})")
## Test Date class
d = Date(2023, 11, 9)
print(f"Date: {d.year}-{d.month}-{d.day}")
## Test error handling
try:
s2 = Stock('AAPL', 50) ## Missing price argument
print("This should not print")
except TypeError as e:
print(f"Error correctly caught: {e}")
Um den Test auszuführen, öffnen Sie Ihr Terminal und führen Sie den folgenden Befehl aus:
python3 test_structure.py
Sie sollten die folgende Ausgabe sehen:
Stock name: GOOG, shares: 100, price: 490.1
Point coordinates: (3, 4)
Date: 2023-11-9
Error correctly caught: Expected 3 arguments
Wie Sie sehen können, funktioniert unsere Basisklasse wie erwartet. Sie hat es viel einfacher gemacht, neue Datenstrukturen zu definieren, ohne den gleichen Boilerplate-Code wiederholt schreiben zu müssen.
Verbesserung der Objektrepräsentation
Unsere Structure-Klasse ist nützlich für die Erstellung und den Zugriff auf Objekte. Derzeit gibt es jedoch keine gute Möglichkeit, sich selbst als Zeichenkette darzustellen. Wenn Sie ein Objekt ausgeben oder es im Python-Interpreter anzeigen lassen, möchten Sie eine klare und informative Darstellung sehen. Dies hilft Ihnen zu verstehen, was das Objekt ist und welche Werte es hat.
Verständnis der Objektrepräsentation in Python
In Python gibt es zwei spezielle Methoden, die verwendet werden, um Objekte auf verschiedene Weise darzustellen. Diese Methoden sind wichtig, da sie es Ihnen ermöglichen, zu steuern, wie Ihre Objekte angezeigt werden.
__str__- Diese Methode wird von derstr()-Funktion und derprint()-Funktion verwendet. Sie liefert eine menschenlesbare Darstellung des Objekts. Beispielsweise könnte die__str__-Methode für einStock-Objekt etwas wie "Stock: GOOG, 100 shares at $490.1" zurückgeben.__repr__- Diese Methode wird vom Python-Interpreter und derrepr()-Funktion verwendet. Sie gibt eine technischere und eindeutige Darstellung des Objekts. Das Ziel von__repr__ist es, eine Zeichenkette bereitzustellen, die verwendet werden kann, um das Objekt neu zu erstellen. Beispielsweise könnte es für einStock-Objekt "Stock('GOOG', 100, 490.1)" zurückgeben.
Fügen wir eine __repr__-Methode zu unserer Structure-Klasse hinzu. Dies erleichtert das Debugging unseres Codes, da wir den Zustand unserer Objekte deutlich sehen können.
Implementierung einer guten Repräsentation
Jetzt müssen Sie Ihre structure.py-Datei aktualisieren. Sie fügen die __repr__-Methode zur Structure-Klasse hinzu. Diese Methode erstellt eine Zeichenkette, die das Objekt auf eine Weise darstellt, die es ermöglicht, es neu zu erstellen.
def __repr__(self):
"""
Return a representation of the object that can be used to recreate it.
Example: Stock('GOOG', 100, 490.1)
"""
## Get the class name
cls_name = type(self).__name__
## Get all the field values
values = [getattr(self, name) for name in self._fields]
## Format the fields and values
args_str = ', '.join(repr(value) for value in values)
## Return the formatted string
return f"{cls_name}({args_str})"
Hier ist, was diese Methode Schritt für Schritt macht:
- Sie ruft den Klassennamen mit
type(self).__name__ab. Dies ist wichtig, da es Ihnen sagt, um welche Art von Objekt es sich handelt. - Sie ruft alle Feldwerte aus der Instanz ab. Dies gibt Ihnen die Daten, die das Objekt enthält.
- Sie erstellt eine Zeichenkettenrepräsentation mit dem Klassennamen und den Werten. Diese Zeichenkette kann verwendet werden, um das Objekt neu zu erstellen.
Testen der verbesserten Repräsentation
Lassen Sie uns unsere verbesserte Implementierung testen. Erstellen Sie eine neue Datei namens test_repr.py. Diese Datei wird einige Instanzen unserer Klassen erstellen und ihre Repräsentationen ausgeben.
## test_repr.py
from structure import Stock, Point, Date
## Create instances
s = Stock('GOOG', 100, 490.1)
p = Point(3, 4)
d = Date(2023, 11, 9)
## Print the representations
print(repr(s))
print(repr(p))
print(repr(d))
## Direct printing also uses __repr__ in the interpreter
print(s)
print(p)
print(d)
Um den Test auszuführen, öffnen Sie Ihr Terminal und geben Sie den folgenden Befehl ein:
python3 test_repr.py
Sie sollten die folgende Ausgabe sehen:
Stock('GOOG', 100, 490.1)
Point(3, 4)
Date(2023, 11, 9)
Stock('GOOG', 100, 490.1)
Point(3, 4)
Date(2023, 11, 9)
Diese Ausgabe ist viel informativer als zuvor. Wenn Sie Stock('GOOG', 100, 490.1) sehen, wissen Sie sofort, was das Objekt darstellt. Sie könnten sogar diese Zeichenkette kopieren und verwenden, um das Objekt in Ihrem Code neu zu erstellen.
Der Nutzen guter Repräsentationen
Eine gute __repr__-Implementierung ist sehr hilfreich für das Debugging. Wenn Sie sich Objekte im Interpreter ansehen oder sie während der Programmausführung protokollieren, erleichtert eine klare Repräsentation die schnelle Identifizierung von Problemen. Sie können den genauen Zustand des Objekts sehen und verstehen, was möglicherweise schief geht.
Beschränkung von Attributnamen
Derzeit erlaubt unsere Structure-Klasse die Festlegung beliebiger Attribute für ihre Instanzen. Für Anfänger mag dies zunächst bequem erscheinen, kann aber tatsächlich zu vielen Problemen führen. Wenn Sie mit einer Klasse arbeiten, erwarten Sie, dass bestimmte Attribute vorhanden sind und auf eine bestimmte Weise verwendet werden. Wenn Benutzer Attributnamen falsch schreiben oder versuchen, Attribute festzulegen, die nicht Teil des ursprünglichen Designs waren, kann dies zu Fehlern führen, die schwer zu finden sind.
Die Notwendigkeit der Attributbeschränkung
Schauen wir uns ein einfaches Szenario an, um zu verstehen, warum wir Attributnamen beschränken müssen. Betrachten Sie den folgenden Code:
s = Stock('GOOG', 100, 490.1)
s.shares = 50 ## Correct attribute name
s.share = 60 ## Typo in attribute name - creates a new attribute instead of updating
In der zweiten Zeile ist ein Tippfehler. Anstelle von shares haben wir share geschrieben. In Python wird anstelle eines Fehlers einfach ein neues Attribut namens share erstellt. Dies kann zu subtilen Fehlern führen, da Sie möglicherweise denken, dass Sie das shares-Attribut aktualisieren, aber tatsächlich erstellen Sie ein neues. Dies kann dazu führen, dass Ihr Code unerwartet verhält und sehr schwierig zu debuggen ist.
Implementierung der Attributbeschränkung
Um dieses Problem zu lösen, können wir die __setattr__-Methode überschreiben. Diese Methode wird jedes Mal aufgerufen, wenn Sie versuchen, ein Attribut für ein Objekt festzulegen. Indem wir sie überschreiben, können wir steuern, welche Attribute festgelegt werden können und welche nicht.
Aktualisieren Sie Ihre Structure-Klasse in structure.py mit dem folgenden Code:
def __setattr__(self, name, value):
"""
Restrict attribute setting to only those defined in _fields
or attributes starting with underscore (private attributes).
"""
if name.startswith('_'):
## Allow setting private attributes (starting with '_')
super().__setattr__(name, value)
elif name in self._fields:
## Allow setting attributes defined in _fields
super().__setattr__(name, value)
else:
## Raise an error for other attributes
raise AttributeError(f'No attribute {name}')
So funktioniert diese Methode:
- Wenn der Attributname mit einem Unterstrich (
_) beginnt, wird er als privates Attribut angesehen. Private Attribute werden oft für interne Zwecke in einer Klasse verwendet. Wir erlauben die Festlegung dieser Attribute, da sie Teil der internen Implementierung der Klasse sind. - Wenn der Attributname in der
_fields-Liste enthalten ist, bedeutet dies, dass es sich um eines der Attribute handelt, die im Klassenentwurf definiert sind. Wir erlauben die Festlegung dieser Attribute, da sie Teil des erwarteten Verhaltens der Klasse sind. - Wenn der Attributname keiner dieser Bedingungen entspricht, werfen wir einen
AttributeError. Dies teilt dem Benutzer mit, dass er versucht, ein Attribut festzulegen, das in der Klasse nicht existiert.
Testen der Attributbeschränkung
Nachdem wir die Attributbeschränkung implementiert haben, lassen Sie uns sie testen, um sicherzustellen, dass sie wie erwartet funktioniert. Erstellen Sie eine Datei namens test_attributes.py mit dem folgenden Code:
## test_attributes.py
from structure import Stock
s = Stock('GOOG', 100, 490.1)
## This should work - valid attribute
print("Setting shares to 50")
s.shares = 50
print(f"Shares is now: {s.shares}")
## This should work - private attribute
print("\nSetting _internal_data")
s._internal_data = "Some data"
print(f"_internal_data is: {s._internal_data}")
## This should fail - invalid attribute
print("\nTrying to set an invalid attribute:")
try:
s.share = 60 ## Typo in attribute name
print("This should not print")
except AttributeError as e:
print(f"Error correctly caught: {e}")
Um den Test auszuführen, öffnen Sie Ihr Terminal und geben Sie den folgenden Befehl ein:
python3 test_attributes.py
Sie sollten die folgende Ausgabe sehen:
Setting shares to 50
Shares is now: 50
Setting _internal_data
_internal_data is: Some data
Trying to set an invalid attribute:
Error correctly caught: No attribute share
Diese Ausgabe zeigt, dass unsere Klasse nun versehentliche Attributfehler verhindert. Sie ermöglicht es uns, gültige Attribute und private Attribute festzulegen, wirft jedoch einen Fehler, wenn wir versuchen, ein ungültiges Attribut festzulegen.
Der Wert der Attributbeschränkung
Die Beschränkung von Attributnamen ist sehr wichtig für die Schreibung von robustem und wartbarem Code. Hier ist warum:
- Sie hilft, Tippfehler in Attributnamen zu erkennen. Wenn Sie einen Fehler beim Eingeben eines Attributnamens machen, wird der Code einen Fehler auslösen, anstatt ein neues Attribut zu erstellen. Dies erleichtert es, Fehler früh im Entwicklungsprozess zu finden und zu beheben.
- Sie verhindert Versuche, Attribute festzulegen, die nicht im Klassenentwurf existieren. Dies stellt sicher, dass die Klasse wie beabsichtigt verwendet wird und dass der Code vorhersehbar verhält.
- Sie vermeidet die versehentliche Erstellung neuer Attribute. Die Erstellung neuer Attribute kann zu unerwartetem Verhalten führen und den Code schwieriger zu verstehen und zu warten machen.
Durch die Beschränkung von Attributnamen machen wir unseren Code zuverlässiger und einfacher zu bearbeiten.
Umgestaltung der Stock-Klasse
Nachdem wir nun eine gut definierte Basisklasse Structure haben, ist es an der Zeit, unsere Stock-Klasse neu zu schreiben. Durch die Verwendung dieser Basisklasse können wir unseren Code vereinfachen und besser organisieren. Die Structure-Klasse bietet eine Reihe von gemeinsamen Funktionen, die wir in unserer Stock-Klasse wiederverwenden können, was ein großer Vorteil für die Wartbarkeit und Lesbarkeit des Codes ist.
Erstellung der neuen Stock-Klasse
Beginnen wir damit, eine neue Datei namens stock.py zu erstellen. Diese Datei wird unsere umgeschriebene Stock-Klasse enthalten. Hier ist der Code, den Sie in die stock.py-Datei einfügen müssen:
## stock.py
from structure import Structure
class Stock(Structure):
_fields = ('name', 'shares', 'price')
@property
def cost(self):
"""
Calculate the cost as shares * price
"""
return self.shares * self.price
def sell(self, nshares):
"""
Sell a number of shares
"""
self.shares -= nshares
Lassen Sie uns analysieren, was diese neue Stock-Klasse tut:
- Sie erbt von der
Structure-Klasse. Dies bedeutet, dass dieStock-Klasse alle von derStructure-Klasse bereitgestellten Funktionen nutzen kann. Einer der Vorteile ist, dass wir keine eigene__init__-Methode schreiben müssen, da dieStructure-Klasse die Attributzuweisung automatisch übernimmt. - Wir definieren
_fields, ein Tupel, das die Attribute derStock-Klasse angibt. Diese Attribute sindname,sharesundprice. - Die Eigenschaft
costwird definiert, um die Gesamtkosten der Aktie zu berechnen. Sie multipliziert die Anzahl dersharesmit demprice. - Die Methode
sellwird verwendet, um die Anzahl der Aktien zu reduzieren. Wenn Sie diese Methode mit einer Anzahl von Aktien zum Verkauf aufrufen, wird diese Anzahl von der aktuellen Anzahl der Aktien subtrahiert.
Testen der neuen Stock-Klasse
Um sicherzustellen, dass unsere neue Stock-Klasse wie erwartet funktioniert, müssen wir eine Testdatei erstellen. Erstellen Sie eine Datei namens test_stock.py mit dem folgenden Code:
## test_stock.py
from stock import Stock
## Create a stock
s = Stock('GOOG', 100, 490.1)
## Check the attributes
print(f"Stock: {s}")
print(f"Name: {s.name}")
print(f"Shares: {s.shares}")
print(f"Price: {s.price}")
print(f"Cost: {s.cost}")
## Sell some shares
print("\nSelling 20 shares...")
s.sell(20)
print(f"Shares after selling: {s.shares}")
print(f"Cost after selling: {s.cost}")
## Try to set an invalid attribute
print("\nTrying to set an invalid attribute:")
try:
s.prices = 500 ## Invalid attribute (should be 'price')
print("This should not print")
except AttributeError as e:
print(f"Error correctly caught: {e}")
In dieser Testdatei importieren wir zunächst die Stock-Klasse aus der stock.py-Datei. Dann erstellen wir eine Instanz der Stock-Klasse mit dem Namen 'GOOG', 100 Aktien und einem Preis von 490,1. Wir geben die Attribute der Aktie aus, um zu überprüfen, ob sie korrekt festgelegt sind. Danach verkaufen wir 20 Aktien und geben die neue Anzahl der Aktien und die neuen Kosten aus. Schließlich versuchen wir, ein ungültiges Attribut prices festzulegen (es sollte price sein). Wenn unsere Stock-Klasse korrekt funktioniert, sollte sie einen AttributeError auslösen.
Um den Test auszuführen, öffnen Sie Ihr Terminal und geben Sie den folgenden Befehl ein:
python3 test_stock.py
Die erwartete Ausgabe ist wie folgt:
Stock: Stock('GOOG', 100, 490.1)
Name: GOOG
Shares: 100
Price: 490.1
Cost: 49010.0
Selling 20 shares...
Shares after selling: 80
Cost after selling: 39208.0
Trying to set an invalid attribute:
Error correctly caught: No attribute prices
Ausführen von Unittests
Wenn Sie aus vorherigen Übungen Unittests haben, können Sie sie gegen Ihre neue Implementierung ausführen. Geben Sie in Ihrem Terminal den folgenden Befehl ein:
python3 teststock.py
Beachten Sie, dass einige Tests fehlschlagen können. Dies kann daran liegen, dass sie bestimmte Verhaltensweisen oder Methoden erwarten, die wir noch nicht implementiert haben. Machen Sie sich keine Sorgen! Wir werden in zukünftigen Übungen auf dieser Grundlage aufbauen.
Rückblick auf unseren Fortschritt
Nehmen wir einen Moment Zeit, um zu überprüfen, was wir bisher erreicht haben:
Wir haben eine wiederverwendbare Basisklasse
Structureerstellt. Diese Klasse:- Übernimmt automatisch die Attributzuweisung, wodurch wir viel wiederholenden Code sparen.
- Bietet eine gute Zeichenkettenrepräsentation, was es erleichtert, unsere Objekte auszugeben und zu debuggen.
- Beschränkt die Attributnamen, um Fehler zu vermeiden, was unseren Code robuster macht.
Wir haben unsere
Stock-Klasse neu geschrieben. Sie:- Erbt von der
Structure-Klasse, um die gemeinsamen Funktionen wiederzuverwenden. - Definiert nur die Felder und domänenspezifischen Methoden, was die Klasse fokussiert und sauber hält.
- Hat ein klares und einfaches Design, das es leicht zu verstehen und zu warten macht.
- Erbt von der
Dieser Ansatz hat mehrere Vorteile für unseren Code:
- Er ist besser wartbar, da wir weniger Wiederholungen haben. Wenn wir etwas in der gemeinsamen Funktionalität ändern müssen, müssen wir es nur in der
Structure-Klasse ändern. - Er ist robuster, aufgrund der besseren Fehlersuche, die von der
Structure-Klasse bereitgestellt wird. - Er ist leichter lesbar, da die Verantwortlichkeiten jeder Klasse klar sind.
In zukünftigen Übungen werden wir auf dieser Grundlage aufbauen, um ein ausgefeilteres System zur Verwaltung von Aktienportfolios zu erstellen.
Zusammenfassung
In diesem Lab haben Sie die Konventionen für die Übergabe von Funktionsargumenten in Python kennengelernt und diese angewandt, um eine besser organisierte und wartbare Codebasis aufzubauen. Sie haben die flexiblen Mechanismen zur Argumentübergabe in Python untersucht, eine wiederverwendbare Basisklasse Structure für Datenobjekte erstellt und die Objektrepräsentation verbessert, um das Debugging zu erleichtern.
Sie haben auch die Attributvalidierung hinzugefügt, um häufige Fehler zu vermeiden, und die Stock-Klasse unter Verwendung der neuen Struktur neu geschrieben. Diese Techniken veranschaulichen wichtige Prinzipien der objektorientierten Programmierung wie die Vererbung zur Codewiederverwendung, die Kapselung zur Gewährleistung der Datenintegrität und die Polymorphie über gemeinsame Schnittstellen. Durch die Anwendung dieser Prinzipien können Sie robusteren und wartbareren Code entwickeln, mit weniger Wiederholungen und weniger Fehlern.