Definitionseigenschaften von Funktionen

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 werden Sie lernen, die grundlegenden Aspekte von Python-Funktionen und -Methoden zu erkunden. Sie werden auch Funktionen flexibler gestalten, indem Sie Parameter effektiv entwerfen.

Darüber hinaus werden Sie Typ-Hinweise (type hints) implementieren, um die Lesbarkeit und Sicherheit des Codes zu verbessern, was für das Schreiben von hochwertigem Python-Code von entscheidender Bedeutung ist.

Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 91% ist. Es hat eine positive Bewertungsrate von 100% von den Lernenden erhalten.

Verständnis des Kontexts

In früheren Übungen haben Sie möglicherweise Code gesehen, der CSV-Dateien liest und die Daten in verschiedenen Datenstrukturen speichert. Das Ziel dieses Codes besteht darin, rohe Textdaten aus einer CSV-Datei zu nehmen und sie in nützlichere Python-Objekte wie Dictionaries oder Klasseninstanzen umzuwandeln. Diese Umwandlung ist essentiell, da sie es uns ermöglicht, mit den Daten in unseren Python-Programmen auf eine strukturierte und sinnvolle Weise zu arbeiten.

Das typische Muster zum Lesen von CSV-Dateien folgt oft einer bestimmten Struktur. Hier ist ein Beispiel für eine Funktion, die eine CSV-Datei liest und jede Zeile in ein Dictionary umwandelt:

import csv

def read_csv_as_dicts(filename, types):
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = { name: func(val)
                       for name, func, val in zip(headers, types, row) }
            records.append(record)
    return records

Lassen Sie uns analysieren, wie diese Funktion funktioniert. Zunächst importiert sie das csv-Modul, das Funktionen zum Arbeiten mit CSV-Dateien in Python bereitstellt. Die Funktion nimmt zwei Parameter entgegen: filename, der der Name der zu lesenden CSV-Datei ist, und types, eine Liste von Funktionen, die zur Umwandlung der Daten in jeder Spalte in den entsprechenden Datentyp verwendet werden.

Innerhalb der Funktion wird eine leere Liste namens records initialisiert, um die Dictionaries zu speichern, die jede Zeile der CSV-Datei repräsentieren. Anschließend wird die Datei mit der with-Anweisung geöffnet, die sicherstellt, dass die Datei nach der Ausführung des Codeblocks ordnungsgemäß geschlossen wird. Die csv.reader-Funktion wird verwendet, um einen Iterator zu erstellen, der jede Zeile der CSV-Datei liest. Die erste Zeile wird als Kopfzeile angenommen und mit der next-Funktion abgerufen.

Als nächstes iteriert die Funktion über die verbleibenden Zeilen in der CSV-Datei. Für jede Zeile wird ein Dictionary mit einer Dictionary-Comprehension erstellt. Die Schlüssel des Dictionaries sind die Spaltenüberschriften, und die Werte sind das Ergebnis der Anwendung der entsprechenden Typkonvertierungsfunktion aus der types-Liste auf den Wert in der Zeile. Schließlich wird das Dictionary der records-Liste hinzugefügt, und die Funktion gibt die Liste der Dictionaries zurück.

Nun schauen wir uns eine ähnliche Funktion an, die Daten aus einer CSV-Datei in Klasseninstanzen liest:

def read_csv_as_instances(filename, cls):
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = cls.from_row(row)
            records.append(record)
    return records

Diese Funktion ähnelt der vorherigen, erstellt jedoch anstelle von Dictionaries Instanzen einer Klasse. Die Funktion nimmt zwei Parameter entgegen: filename, der der Name der zu lesenden CSV-Datei ist, und cls, die Klasse, deren Instanzen erstellt werden sollen.

Innerhalb der Funktion folgt sie einer ähnlichen Struktur wie die vorherige Funktion. Sie initialisiert eine leere Liste namens records, um die Klasseninstanzen zu speichern. Anschließend wird die Datei geöffnet, die Kopfzeilen gelesen und über die verbleibenden Zeilen iteriert. Für jede Zeile wird die from_row-Methode der Klasse cls aufgerufen, um eine Instanz der Klasse mit den Daten aus der Zeile zu erstellen. Die Instanz wird dann der records-Liste hinzugefügt, und die Funktion gibt die Liste der Instanzen zurück.

In diesem Lab werden wir diese Funktionen refaktorisieren, um sie flexibler und robuster zu machen. Wir werden auch Python's Typ-Hinweis-System (type hinting system) untersuchen, das es uns ermöglicht, die erwarteten Typen der Parameter und Rückgabewerte unserer Funktionen anzugeben. Dies kann unseren Code lesbarer und leichter verständlich machen, insbesondere für andere Entwickler, die möglicherweise mit unserem Code arbeiten.

Lassen Sie uns beginnen, indem wir eine reader.py-Datei erstellen und diese Anfangsfunktionen hinzufügen. Stellen Sie sicher, dass Sie diese Funktionen testen, um sicherzustellen, dass sie ordnungsgemäß funktionieren, bevor Sie mit den nächsten Schritten fortfahren.

Erstellen der grundlegenden CSV-Reader-Funktionen

Beginnen wir damit, eine reader.py-Datei mit zwei grundlegenden Funktionen zum Lesen von CSV-Daten zu erstellen. Diese Funktionen werden uns helfen, CSV-Dateien auf verschiedene Weise zu verarbeiten, wie z. B. die Daten in Dictionaries oder Klasseninstanzen umzuwandeln.

Zunächst müssen wir verstehen, was eine CSV-Datei ist. CSV steht für Comma-Separated Values (Komma-getrennte Werte). Es ist ein einfaches Dateiformat, das zur Speicherung tabellarischer Daten verwendet wird, wobei jede Zeile eine Zeile darstellt und die Werte in jeder Zeile durch Kommas getrennt sind.

Jetzt erstellen wir die reader.py-Datei. Befolgen Sie diese Schritte:

  1. Öffnen Sie den Code-Editor und erstellen Sie eine neue Datei namens reader.py im Verzeichnis /home/labex/project. Hier werden wir unsere Funktionen zum Lesen von CSV-Daten schreiben.

  2. Fügen Sie den folgenden Code in die reader.py-Datei ein:

## reader.py

import csv

def read_csv_as_dicts(filename, types):
    '''
    Read CSV data into a list of dictionaries with optional type conversion

    Args:
        filename: Path to the CSV file
        types: List of type conversion functions for each column

    Returns:
        List of dictionaries with data from the CSV file
    '''
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = { name: func(val)
                       for name, func, val in zip(headers, types, row) }
            records.append(record)
    return records

def read_csv_as_instances(filename, cls):
    '''
    Read CSV data into a list of class instances

    Args:
        filename: Path to the CSV file
        cls: Class to create instances from

    Returns:
        List of class instances with data from the CSV file
    '''
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = cls.from_row(row)
            records.append(record)
    return records

In der read_csv_as_dicts-Funktion öffnen wir zunächst die CSV-Datei mit der open-Funktion. Dann verwenden wir die csv.reader-Funktion, um die Datei Zeile für Zeile zu lesen. Die Anweisung next(rows) liest die erste Zeile der Datei, die normalerweise die Kopfzeilen enthält. Danach iterieren wir über die verbleibenden Zeilen. Für jede Zeile erstellen wir ein Dictionary, bei dem die Schlüssel die Kopfzeilen sind und die Werte die entsprechenden Werte in der Zeile sind, mit optionaler Typkonvertierung mithilfe der types-Liste.

Die read_csv_as_instances-Funktion ist ähnlich, erstellt jedoch anstelle von Dictionaries Instanzen einer gegebenen Klasse. Sie geht davon aus, dass die Klasse eine statische Methode namens from_row hat, die eine Instanz aus einer Datenzeile erstellen kann.

  1. Testen wir diese Funktionen, um sicherzustellen, dass sie korrekt funktionieren. Erstellen Sie eine neue Datei namens test_reader.py mit dem folgenden Code:
## test_reader.py

import reader
import stock

## Test reading CSV as dictionaries
portfolio_dicts = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("First portfolio item as dictionary:", portfolio_dicts[0])
print("Total items:", len(portfolio_dicts))

## Test reading CSV as class instances
portfolio_instances = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
print("\nFirst portfolio item as Stock instance:", portfolio_instances[0])
print("Total items:", len(portfolio_instances))

In der test_reader.py-Datei importieren wir das reader-Modul, das wir gerade erstellt haben, und das stock-Modul. Dann testen wir die beiden Funktionen, indem wir sie mit einer Beispiel-CSV-Datei namens portfolio.csv aufrufen. Wir geben das erste Element und die Gesamtzahl der Elemente im Portfolio aus, um zu überprüfen, ob die Funktionen wie erwartet funktionieren.

  1. Führen Sie das Testskript aus dem Terminal aus:
python test_reader.py

Die Ausgabe sollte in etwa so aussehen:

First portfolio item as dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
Total items: 7

First portfolio item as Stock instance: Stock('AA', 100, 32.2)
Total items: 7

Dies bestätigt, dass unsere beiden Funktionen korrekt funktionieren. Die erste Funktion wandelt CSV-Daten in eine Liste von Dictionaries mit korrekter Typkonvertierung um, und die zweite Funktion erstellt Klasseninstanzen mithilfe einer statischen Methode der angegebenen Klasse.

Im nächsten Schritt werden wir diese Funktionen refaktorisieren, um sie flexibler zu machen, indem wir es ihnen ermöglichen, mit jeder iterierbaren Datenquelle zu arbeiten, nicht nur mit Dateinamen.

✨ Lösung prüfen und üben

Machbarkeit der Funktionen erhöhen

Derzeit sind unsere Funktionen darauf beschränkt, aus Dateien zu lesen, die durch einen Dateinamen angegeben werden. Dies schränkt ihre Anwendbarkeit ein. In der Programmierung ist es oft vorteilhaft, Funktionen flexibler zu gestalten, damit sie verschiedene Arten von Eingaben verarbeiten können. In unserem Fall wäre es toll, wenn unsere Funktionen mit jedem iterierbaren Objekt arbeiten könnten, das Zeilen erzeugt, wie z. B. Dateiobjekte oder andere Datenquellen. So können wir diese Funktionen in mehr Szenarien verwenden, wie z. B. das Lesen aus komprimierten Dateien oder anderen Datenströmen.

Lassen Sie uns unseren Code refaktorisieren, um diese Flexibilität zu ermöglichen:

  1. Öffnen Sie die reader.py-Datei. Wir werden sie ändern, um einige neue Funktionen hinzuzufügen. Diese neuen Funktionen ermöglichen es unserem Code, mit verschiedenen Arten von iterierbaren Objekten zu arbeiten. Hier ist der Code, den Sie hinzufügen müssen:
## reader.py

import csv

def csv_as_dicts(lines, types):
    '''
    Parse CSV data from an iterable into a list of dictionaries

    Args:
        lines: An iterable producing CSV lines
        types: List of type conversion functions for each column

    Returns:
        List of dictionaries with data from the CSV lines
    '''
    records = []
    rows = csv.reader(lines)
    headers = next(rows)
    for row in rows:
        record = { name: func(val)
                  for name, func, val in zip(headers, types, row) }
        records.append(record)
    return records

def csv_as_instances(lines, cls):
    '''
    Parse CSV data from an iterable into a list of class instances

    Args:
        lines: An iterable producing CSV lines
        cls: Class to create instances from

    Returns:
        List of class instances with data from the CSV lines
    '''
    records = []
    rows = csv.reader(lines)
    headers = next(rows)
    for row in rows:
        record = cls.from_row(row)
        records.append(record)
    return records

def read_csv_as_dicts(filename, types):
    '''
    Read CSV data into a list of dictionaries with optional type conversion

    Args:
        filename: Path to the CSV file
        types: List of type conversion functions for each column

    Returns:
        List of dictionaries with data from the CSV file
    '''
    with open(filename) as file:
        return csv_as_dicts(file, types)

def read_csv_as_instances(filename, cls):
    '''
    Read CSV data into a list of class instances

    Args:
        filename: Path to the CSV file
        cls: Class to create instances from

    Returns:
        List of class instances with data from the CSV file
    '''
    with open(filename) as file:
        return csv_as_instances(file, cls)

Schauen wir uns genauer an, wie wir den Code refaktoriert haben:

  1. Wir haben zwei allgemeinere Funktionen, csv_as_dicts() und csv_as_instances(), erstellt. Diese Funktionen sind so konzipiert, dass sie mit jedem iterierbaren Objekt arbeiten können, das CSV-Zeilen erzeugt. Das bedeutet, dass sie verschiedene Arten von Eingabequellen verarbeiten können, nicht nur Dateien, die durch einen Dateinamen angegeben werden.

  2. Wir haben read_csv_as_dicts() und read_csv_as_instances() neu implementiert, um diese neuen Funktionen zu verwenden. Auf diese Weise ist die ursprüngliche Funktionalität des Lesens aus einer Datei über den Dateinamen weiterhin verfügbar, aber jetzt basiert sie auf den flexibleren Funktionen.

  3. Dieser Ansatz gewährleistet die Rückwärtskompatibilität mit bestehendem Code. Das bedeutet, dass jeder Code, der die alten Funktionen verwendet hat, weiterhin wie erwartet funktioniert. Gleichzeitig wird unsere Bibliothek flexibler, da sie jetzt verschiedene Arten von Eingabequellen verarbeiten kann.

  4. Jetzt testen wir diese neuen Funktionen. Erstellen Sie eine Datei namens test_reader_flexibility.py und fügen Sie den folgenden Code hinzu. Dieser Code wird die neuen Funktionen mit verschiedenen Arten von Eingabequellen testen:

## test_reader_flexibility.py

import reader
import stock
import gzip

## Test opening a regular file
with open('portfolio.csv') as file:
    portfolio = reader.csv_as_dicts(file, [str, int, float])
    print("First item from open file:", portfolio[0])

## Test opening a gzipped file
with gzip.open('portfolio.csv.gz', 'rt') as file:  ## 'rt' means read text
    portfolio = reader.csv_as_instances(file, stock.Stock)
    print("\nFirst item from gzipped file:", portfolio[0])

## Test backward compatibility
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("\nFirst item using backward compatible function:", portfolio[0])
  1. Nachdem Sie die Testdatei erstellt haben, müssen Sie das Testskript aus dem Terminal ausführen. Öffnen Sie Ihr Terminal und navigieren Sie zum Verzeichnis, in dem sich die test_reader_flexibility.py-Datei befindet. Führen Sie dann den folgenden Befehl aus:
python test_reader_flexibility.py

Die Ausgabe sollte in etwa so aussehen:

First item from open file: {'name': 'AA', 'shares': 100, 'price': 32.2}

First item from gzipped file: Stock('AA', 100, 32.2)

First item using backward compatible function: {'name': 'AA', 'shares': 100, 'price': 32.2}

Diese Ausgabe bestätigt, dass unsere Funktionen jetzt mit verschiedenen Arten von Eingabequellen arbeiten, während die Rückwärtskompatibilität gewährleistet bleibt. Die refaktorierten Funktionen können Daten verarbeiten von:

  • Normalen Dateien, die mit open() geöffnet werden
  • Komprimierten Dateien, die mit gzip.open() geöffnet werden
  • Jedem anderen iterierbaren Objekt, das Textzeilen erzeugt

Dies macht unseren Code viel flexibler und einfacher in verschiedenen Szenarien zu verwenden.

✨ Lösung prüfen und üben

Umgang mit CSV-Dateien ohne Kopfzeilen

In der Welt der Datenverarbeitung haben nicht alle CSV-Dateien Kopfzeilen in ihrer ersten Zeile. Kopfzeilen sind die Namen, die jeder Spalte in einer CSV-Datei gegeben werden und uns helfen, zu verstehen, welche Art von Daten jede Spalte enthält. Wenn eine CSV-Datei keine Kopfzeilen hat, müssen wir einen Weg finden, sie richtig zu verarbeiten. In diesem Abschnitt werden wir unsere Funktionen ändern, um es dem Aufrufer zu ermöglichen, die Kopfzeilen manuell anzugeben, damit wir sowohl mit CSV-Dateien mit als auch ohne Kopfzeilen arbeiten können.

  1. Öffnen Sie die reader.py-Datei und aktualisieren Sie sie, um die Verarbeitung von Kopfzeilen einzubeziehen:
## reader.py

import csv

def csv_as_dicts(lines, types, headers=None):
    '''
    Parse CSV data from an iterable into a list of dictionaries

    Args:
        lines: An iterable producing CSV lines
        types: List of type conversion functions for each column
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of dictionaries with data from the CSV lines
    '''
    records = []
    rows = csv.reader(lines)

    if headers is None:
        ## Use the first row as headers if none provided
        headers = next(rows)

    for row in rows:
        record = { name: func(val)
                  for name, func, val in zip(headers, types, row) }
        records.append(record)
    return records

def csv_as_instances(lines, cls, headers=None):
    '''
    Parse CSV data from an iterable into a list of class instances

    Args:
        lines: An iterable producing CSV lines
        cls: Class to create instances from
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of class instances with data from the CSV lines
    '''
    records = []
    rows = csv.reader(lines)

    if headers is None:
        ## Skip the first row if no headers provided
        next(rows)

    for row in rows:
        record = cls.from_row(row)
        records.append(record)
    return records

def read_csv_as_dicts(filename, types, headers=None):
    '''
    Read CSV data into a list of dictionaries with optional type conversion

    Args:
        filename: Path to the CSV file
        types: List of type conversion functions for each column
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of dictionaries with data from the CSV file
    '''
    with open(filename) as file:
        return csv_as_dicts(file, types, headers)

def read_csv_as_instances(filename, cls, headers=None):
    '''
    Read CSV data into a list of class instances

    Args:
        filename: Path to the CSV file
        cls: Class to create instances from
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of class instances with data from the CSV file
    '''
    with open(filename) as file:
        return csv_as_instances(file, cls, headers)

Lassen Sie uns die wichtigsten Änderungen verstehen, die wir an diesen Funktionen vorgenommen haben:

  1. Wir haben allen Funktionen einen headers-Parameter hinzugefügt und dessen Standardwert auf None gesetzt. Dies bedeutet, dass wenn der Aufrufer keine Kopfzeilen angibt, die Funktionen das Standardverhalten verwenden.

  2. In der csv_as_dicts-Funktion verwenden wir die erste Zeile als Kopfzeilen nur, wenn der headers-Parameter None ist. Dies ermöglicht es uns, Dateien mit Kopfzeilen automatisch zu verarbeiten.

  3. In der csv_as_instances-Funktion überspringen wir die erste Zeile nur, wenn der headers-Parameter None ist. Dies liegt daran, dass wenn wir unsere eigenen Kopfzeilen angeben, die erste Zeile der Datei tatsächliche Daten und keine Kopfzeilen ist.

  4. Testen wir diese Änderungen mit unserer Datei ohne Kopfzeilen. Erstellen Sie eine Datei namens test_headers.py:

## test_headers.py

import reader
import stock

## Define column names for the file without headers
column_names = ['name', 'shares', 'price']

## Test reading a file without headers
portfolio = reader.read_csv_as_dicts('portfolio_noheader.csv',
                                     [str, int, float],
                                     headers=column_names)
print("First item from file without headers:", portfolio[0])
print("Total items:", len(portfolio))

## Test reading the same file as instances
portfolio = reader.read_csv_as_instances('portfolio_noheader.csv',
                                        stock.Stock,
                                        headers=column_names)
print("\nFirst item as Stock instance:", portfolio[0])
print("Total items:", len(portfolio))

## Verify that original functionality still works
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("\nFirst item from file with headers:", portfolio[0])

In diesem Testskript definieren wir zunächst die Spaltennamen für die Datei ohne Kopfzeilen. Dann testen wir das Lesen der Datei ohne Kopfzeilen als Liste von Dictionaries und als Liste von Klasseninstanzen. Schließlich überprüfen wir, ob die ursprüngliche Funktionalität weiterhin funktioniert, indem wir eine Datei mit Kopfzeilen lesen.

  1. Führen Sie das Testskript aus dem Terminal aus:
python test_headers.py

Die Ausgabe sollte in etwa so aussehen:

First item from file without headers: {'name': 'AA', 'shares': 100, 'price': 32.2}
Total items: 7

First item as Stock instance: Stock('AA', 100, 32.2)
Total items: 7

First item from file with headers: {'name': 'AA', 'shares': 100, 'price': 32.2}

Diese Ausgabe bestätigt, dass unsere Funktionen jetzt sowohl CSV-Dateien mit als auch ohne Kopfzeilen verarbeiten können. Der Benutzer kann bei Bedarf Spaltennamen angeben oder sich auf das Standardverhalten des Lesens von Kopfzeilen aus der ersten Zeile verlassen.

Durch diese Änderung sind unsere CSV-Reader-Funktionen jetzt vielseitiger und können eine größere Bandbreite von Dateiformaten verarbeiten. Dies ist ein wichtiger Schritt, um unseren Code robuster und in verschiedenen Szenarien nützlicher zu machen.

Hinzufügen von Typ-Hinweisen

In Python 3.5 und späteren Versionen werden Typ-Hinweise (type hints) unterstützt. Typ-Hinweise sind eine Möglichkeit, die erwarteten Datentypen von Variablen, Funktionsparametern und Rückgabewerten in Ihrem Code anzugeben. Sie ändern nicht, wie der Code ausgeführt wird, sondern machen den Code lesbarer und können helfen, bestimmte Arten von Fehlern zu erkennen, bevor der Code tatsächlich ausgeführt wird. Jetzt fügen wir Typ-Hinweise zu unseren CSV-Reader-Funktionen hinzu.

  1. Öffnen Sie die reader.py-Datei und aktualisieren Sie sie, um Typ-Hinweise einzubeziehen:
## reader.py

import csv
from typing import List, Callable, Dict, Any, Type, Optional, TextIO, Iterator, TypeVar

## Define a generic type for the class parameter
T = TypeVar('T')

def csv_as_dicts(lines: Iterator[str],
                types: List[Callable[[str], Any]],
                headers: Optional[List[str]] = None) -> List[Dict[str, Any]]:
    '''
    Parse CSV data from an iterable into a list of dictionaries

    Args:
        lines: An iterable producing CSV lines
        types: List of type conversion functions for each column
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of dictionaries with data from the CSV lines
    '''
    records: List[Dict[str, Any]] = []
    rows = csv.reader(lines)

    if headers is None:
        ## Use the first row as headers if none provided
        headers = next(rows)

    for row in rows:
        record = { name: func(val)
                  for name, func, val in zip(headers, types, row) }
        records.append(record)
    return records

def csv_as_instances(lines: Iterator[str],
                    cls: Type[T],
                    headers: Optional[List[str]] = None) -> List[T]:
    '''
    Parse CSV data from an iterable into a list of class instances

    Args:
        lines: An iterable producing CSV lines
        cls: Class to create instances from
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of class instances with data from the CSV lines
    '''
    records: List[T] = []
    rows = csv.reader(lines)

    if headers is None:
        ## Skip the first row if no headers provided
        next(rows)

    for row in rows:
        record = cls.from_row(row)
        records.append(record)
    return records

def read_csv_as_dicts(filename: str,
                     types: List[Callable[[str], Any]],
                     headers: Optional[List[str]] = None) -> List[Dict[str, Any]]:
    '''
    Read CSV data into a list of dictionaries with optional type conversion

    Args:
        filename: Path to the CSV file
        types: List of type conversion functions for each column
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of dictionaries with data from the CSV file
    '''
    with open(filename) as file:
        return csv_as_dicts(file, types, headers)

def read_csv_as_instances(filename: str,
                         cls: Type[T],
                         headers: Optional[List[str]] = None) -> List[T]:
    '''
    Read CSV data into a list of class instances

    Args:
        filename: Path to the CSV file
        cls: Class to create instances from
        headers: Optional list of column names. If None, first row is used as headers

    Returns:
        List of class instances with data from the CSV file
    '''
    with open(filename) as file:
        return csv_as_instances(file, cls, headers)

Lassen Sie uns die wichtigsten Änderungen verstehen, die wir im Code vorgenommen haben:

  1. Wir haben Typen aus dem typing-Modul importiert. Dieses Modul bietet eine Reihe von Typen, die wir verwenden können, um Typ-Hinweise zu definieren. Beispielsweise sind List, Dict und Optional Typen aus diesem Modul.

  2. Wir haben eine generische Typvariable T hinzugefügt, um den Klassentyp darzustellen. Eine generische Typvariable ermöglicht es uns, Funktionen zu schreiben, die auf sichere Weise mit verschiedenen Typen arbeiten können.

  3. Wir haben Typ-Hinweise zu allen Funktionsparametern und Rückgabewerten hinzugefügt. Dies macht klar, welche Typen von Argumenten eine Funktion erwartet und welchen Typ von Wert sie zurückgibt.

  4. Wir haben geeignete Containertypen wie List, Dict und Optional verwendet. List repräsentiert eine Liste, Dict ein Wörterbuch und Optional gibt an, dass ein Parameter entweder einen bestimmten Typ haben oder None sein kann.

  5. Wir haben Callable für die Typkonvertierungsfunktionen verwendet. Callable wird verwendet, um anzugeben, dass ein Parameter eine aufrufbare Funktion ist.

  6. Wir haben das generische T verwendet, um auszudrücken, dass csv_as_instances eine Liste von Instanzen der übergebenen Klasse zurückgibt. Dies hilft der IDE und anderen Tools, den Typ der zurückgegebenen Objekte zu verstehen.

  7. Jetzt erstellen wir eine einfache Testdatei, um sicherzustellen, dass alles weiterhin richtig funktioniert:

## test_types.py

import reader
import stock

## The functions should work exactly as before
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("First item:", portfolio[0])

## But now we have better type checking and IDE support
stock_portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
print("\nFirst stock:", stock_portfolio[0])

## We can see that stock_portfolio is a list of Stock objects
## This helps IDEs provide better code completion
first_stock = stock_portfolio[0]
print(f"\nName: {first_stock.name}")
print(f"Shares: {first_stock.shares}")
print(f"Price: {first_stock.price}")
print(f"Value: {first_stock.shares * first_stock.price}")
  1. Führen Sie das Testskript aus dem Terminal aus:
python test_types.py

Die Ausgabe sollte in etwa so aussehen:

First item: {'name': 'AA', 'shares': 100, 'price': 32.2}

First stock: Stock('AA', 100, 32.2)

Name: AA
Shares: 100
Price: 32.2
Value: 3220.0

Typ-Hinweise ändern nicht, wie der Code ausgeführt wird, sondern bieten mehrere Vorteile:

  1. Sie bieten eine bessere IDE-Unterstützung mit Code-Vervollständigung. Wenn Sie eine IDE wie PyCharm oder VS Code verwenden, kann diese die Typ-Hinweise nutzen, um die richtigen Methoden und Attribute für Ihre Variablen vorzuschlagen.
  2. Sie liefern klarere Dokumentation über die erwarteten Parameter- und Rückgabetypen. Nur durch Betrachten der Funktionsdefinition können Sie feststellen, welche Typen von Argumenten sie erwartet und welchen Typ von Wert sie zurückgibt.
  3. Sie ermöglichen es Ihnen, statische Typ-Checker wie mypy zu verwenden, um Fehler frühzeitig zu erkennen. Statische Typ-Checker analysieren Ihren Code, ohne ihn auszuführen, und können typbezogene Fehler finden, bevor Sie den Code ausführen.
  4. Sie verbessern die Lesbarkeit und Wartbarkeit des Codes. Wenn Sie oder andere Entwickler später wieder zum Code zurückkehren, ist es einfacher zu verstehen, was der Code tut.

In einer großen Codebasis können diese Vorteile die Anzahl von Fehlern erheblich reduzieren und den Code leichter verständlich und wartbar machen.

Hinweis: Typ-Hinweise sind in Python optional, werden aber zunehmend in professionellem Code verwendet. Bibliotheken wie die im Python-Standardbibliothek und viele beliebte Drittanbieter-Pakete enthalten jetzt umfangreiche Typ-Hinweise.

Zusammenfassung

In diesem Lab haben Sie mehrere Schlüsselaspekte des Funktionsdesigns in Python gelernt. Zunächst haben Sie das grundlegende Funktionsdesign kennengelernt, insbesondere wie man Funktionen schreibt, um CSV-Daten in verschiedene Datenstrukturen zu verarbeiten. Sie haben auch die Flexibilität von Funktionen erkundet, indem Sie Funktionen umgestaltet haben, um mit jeder iterierbaren Quelle zu arbeiten, was die Vielseitigkeit und Wiederverwendbarkeit des Codes verbessert.

Darüber hinaus haben Sie gelernt, optionale Parameter hinzuzufügen, um verschiedene Anwendungsfälle zu behandeln, wie z. B. CSV-Dateien mit oder ohne Kopfzeilen, und Python's Typ-Hinweis-System (type hinting) zu nutzen, um die Lesbarkeit und Wartbarkeit des Codes zu verbessern. Diese Fähigkeiten sind entscheidend für das Schreiben von robustem Python-Code, und wenn Ihre Programme komplexer werden, werden diese Entwurfsprinzipien Ihren Code organisiert und verständlich halten. Die Techniken können auch außerhalb der CSV-Verarbeitung angewendet werden, was sie zu einem wertvollen Bestandteil Ihres Python-Programmier-Werkzeugsatzes macht.