Konventionen für die Übergabe von Funktionsargumenten

PythonPythonBeginner
Jetzt üben

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

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:

  1. Sie definiert eine Klassenvariable _fields. Standardmäßig ist diese Variable leer. Diese Variable wird die Namen der Attribute enthalten, die die Klasse haben wird.
  2. Sie prüft, ob die Anzahl der an den Konstruktor übergebenen Argumente mit der Anzahl der in _fields definierten Felder übereinstimmt. Wenn sie nicht übereinstimmen, wird ein TypeError ausgelöst. Dies hilft uns, Fehler frühzeitig zu erkennen.
  3. 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.

✨ Lösung prüfen und üben

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 der str()-Funktion und der print()-Funktion verwendet. Sie liefert eine menschenlesbare Darstellung des Objekts. Beispielsweise könnte die __str__-Methode für ein Stock-Objekt etwas wie "Stock: GOOG, 100 shares at $490.1" zurückgeben.
  • __repr__ - Diese Methode wird vom Python-Interpreter und der repr()-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 ein Stock-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:

  1. Sie ruft den Klassennamen mit type(self).__name__ ab. Dies ist wichtig, da es Ihnen sagt, um welche Art von Objekt es sich handelt.
  2. Sie ruft alle Feldwerte aus der Instanz ab. Dies gibt Ihnen die Daten, die das Objekt enthält.
  3. 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.

✨ Lösung prüfen und üben

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:

  1. 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.
  2. 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.
  3. 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:

  1. 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.
  2. 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.
  3. 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.

✨ Lösung prüfen und üben

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:

  1. Sie erbt von der Structure-Klasse. Dies bedeutet, dass die Stock-Klasse alle von der Structure-Klasse bereitgestellten Funktionen nutzen kann. Einer der Vorteile ist, dass wir keine eigene __init__-Methode schreiben müssen, da die Structure-Klasse die Attributzuweisung automatisch übernimmt.
  2. Wir definieren _fields, ein Tupel, das die Attribute der Stock-Klasse angibt. Diese Attribute sind name, shares und price.
  3. Die Eigenschaft cost wird definiert, um die Gesamtkosten der Aktie zu berechnen. Sie multipliziert die Anzahl der shares mit dem price.
  4. Die Methode sell wird 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:

  1. Wir haben eine wiederverwendbare Basisklasse Structure erstellt. 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.
  2. 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.

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.

✨ Lösung prüfen und üben

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.