Python's Höhere Funktionen

Beginner

This tutorial is from open-source community. Access the source code

Einführung

In diesem Lab werden Sie sich mit höheren Funktionen (higher-order functions) in Python vertraut machen. Höhere Funktionen können andere Funktionen als Argumente akzeptieren oder Funktionen als Ergebnisse zurückgeben. Dieses Konzept ist in der funktionalen Programmierung von entscheidender Bedeutung und ermöglicht es Ihnen, modulareres und wiederverwendbares Code zu schreiben.

Sie werden verstehen, was höhere Funktionen sind, eine solche Funktion erstellen, die eine andere Funktion als Argument nimmt, bestehende Funktionen so umgestalten, dass sie eine höhere Funktion verwenden, und die integrierte map()-Funktion von Python nutzen. Die Datei reader.py wird während des Labs geändert.

Verständnis von Code-Duplizierung

Beginnen wir damit, uns den aktuellen Code in der Datei reader.py anzusehen. Beim Programmieren ist es ein wichtiger Schritt, bestehenden Code zu untersuchen, um zu verstehen, wie die Dinge funktionieren und Bereiche für Verbesserungen zu identifizieren. Sie können die Datei reader.py im WebIDE öffnen. Es gibt zwei Möglichkeiten, dies zu tun. Sie können auf die Datei im Dateiexplorer klicken, oder Sie können die folgenden Befehle im Terminal ausführen. Diese Befehle navigieren zunächst zum Projektverzeichnis und zeigen dann den Inhalt der Datei reader.py an.

cd ~/project
cat reader.py

Wenn Sie sich den Code ansehen, werden Sie feststellen, dass es zwei Funktionen gibt. Funktionen in Python sind Codeblöcke, die eine bestimmte Aufgabe ausführen. Hier sind die beiden Funktionen und was sie tun:

  1. csv_as_dicts(): Diese Funktion nimmt CSV-Daten und wandelt sie in eine Liste von Dictionaries um. Ein Dictionary in Python ist eine Sammlung von Schlüssel-Wert-Paaren, die nützlich ist, um Daten strukturiert zu speichern.
  2. csv_as_instances(): Diese Funktion nimmt CSV-Daten und wandelt sie in eine Liste von Instanzen um. Eine Instanz ist ein Objekt, das aus einer Klasse erstellt wird, die ein Bauplan für die Erstellung von Objekten ist.

Lassen Sie uns nun genauer auf diese beiden Funktionen schauen. Sie werden feststellen, dass sie ziemlich ähnlich sind. Beide Funktionen folgen diesen Schritten:

  • Zunächst initialisieren sie eine leere records-Liste. Eine Liste in Python ist eine Sammlung von Elementen, die unterschiedlicher Typen sein können. Das Initialisieren einer leeren Liste bedeutet, eine Liste ohne Elemente zu erstellen, die zur Speicherung der verarbeiteten Daten verwendet wird.
  • Dann verwenden sie csv.reader(), um die Eingabe zu parsen. Parsen bedeutet, die Eingabedaten zu analysieren, um sinnvolle Informationen zu extrahieren. In diesem Fall hilft uns csv.reader(), die CSV-Daten Zeile für Zeile zu lesen.
  • Sie behandeln die Header auf die gleiche Weise. Header in einer CSV-Datei sind die erste Zeile, die normalerweise die Namen der Spalten enthält.
  • Danach durchlaufen sie jede Zeile in den CSV-Daten in einer Schleife. Eine Schleife ist ein Programmierkonstrukt, das es Ihnen ermöglicht, einen Codeblock mehrmals auszuführen.
  • Für jede Zeile verarbeiten sie diese, um einen Datensatz zu erstellen. Dieser Datensatz kann entweder ein Dictionary oder eine Instanz sein, je nach Funktion.
  • Sie fügen den Datensatz der records-Liste hinzu. Hinzufügen bedeutet, ein Element ans Ende der Liste anzufügen.
  • Schließlich geben sie die records-Liste zurück, die alle verarbeiteten Daten enthält.

Diese Duplizierung von Code ist aus mehreren Gründen ein Problem. Wenn Code dupliziert wird:

  • Wird er schwieriger zu warten. Wenn Sie eine Änderung am Code vornehmen müssen, müssen Sie die gleiche Änderung an mehreren Stellen vornehmen. Dies erfordert mehr Zeit und Aufwand.
  • Müssen alle Änderungen an mehreren Stellen implementiert werden. Dies erhöht die Wahrscheinlichkeit, dass Sie vergessen, die Änderung an einer der Stellen vorzunehmen, was zu inkonsistentem Verhalten führt.
  • Es erhöht auch die Wahrscheinlichkeit, dass Fehler eingeführt werden. Fehler sind Fehler im Code, die dazu führen können, dass er unerwartet verhält.

Der einzige echte Unterschied zwischen diesen beiden Funktionen ist, wie sie eine Zeile in einen Datensatz umwandeln. Dies ist eine klassische Situation, in der eine höhere Funktion (higher-order function) sehr nützlich sein kann. Eine höhere Funktion ist eine Funktion, die eine andere Funktion als Argument nehmen oder eine Funktion als Ergebnis zurückgeben kann.

Lassen Sie uns einige Beispielverwendungen dieser Funktionen betrachten, um besser zu verstehen, wie sie funktionieren. Der folgende Code zeigt, wie man csv_as_dicts() und csv_as_instances() verwendet:

## Example of using csv_as_dicts
with open('portfolio.csv') as f:
    portfolio = csv_as_dicts(f, [str, int, float])
print(portfolio[0])  ## {'name': 'AA', 'shares': 100, 'price': 32.2}

## Example of using csv_as_instances
class Stock:
    @classmethod
    def from_row(cls, row):
        return cls(row[0], int(row[1]), float(row[2]))

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

with open('portfolio.csv') as f:
    portfolio = csv_as_instances(f, Stock)
print(portfolio[0].name, portfolio[0].shares, portfolio[0].price)  ## AA 100 32.2

Im nächsten Schritt werden wir eine höhere Funktion erstellen, um diese Code-Duplizierung zu beseitigen. Dies wird den Code wartbarer und fehleranfälliger machen.

Erstellen einer höheren Funktion (Higher-Order Function)

In Python ist eine höhere Funktion (Higher-Order Function) eine Funktion, die eine andere Funktion als Argument nehmen kann. Dies ermöglicht eine größere Flexibilität und Code-Wiederverwendung. Jetzt erstellen wir eine höhere Funktion namens convert_csv(). Diese Funktion wird die gemeinsamen Operationen bei der Verarbeitung von CSV-Daten übernehmen, während Sie festlegen können, wie jede Zeile der CSV-Datei in einen Datensatz umgewandelt wird.

Öffnen Sie die Datei reader.py im WebIDE. Wir werden eine Funktion hinzufügen, die ein iterierbares Objekt mit CSV-Daten, eine Konvertierungsfunktion und optional Spaltenüberschriften (Headers) nimmt. Die Konvertierungsfunktion wird verwendet, um jede Zeile der CSV-Datei in einen Datensatz umzuwandeln.

Hier ist der Code für die convert_csv()-Funktion. Kopieren Sie ihn und fügen Sie ihn in Ihre reader.py-Datei ein:

def convert_csv(lines, conversion_func, *, headers=None):
    '''
    Convert lines of CSV data using the provided conversion function

    Args:
        lines: An iterable containing CSV data
        conversion_func: A function that takes headers and a row and returns a record
        headers: Column headers (optional). If None, the first row is used as headers

    Returns:
        A list of records as processed by conversion_func
    '''
    records = []
    rows = csv.reader(lines)
    if headers is None:
        headers = next(rows)
    for row in rows:
        record = conversion_func(headers, row)
        records.append(record)
    return records

Lassen Sie uns analysieren, was diese Funktion tut. Zunächst initialisiert sie eine leere Liste namens records, um die konvertierten Datensätze zu speichern. Dann verwendet sie die csv.reader()-Funktion, um die Zeilen der CSV-Daten zu lesen. Wenn keine Spaltenüberschriften angegeben werden, nimmt sie die erste Zeile als Spaltenüberschriften. Für jede nachfolgende Zeile wendet sie die conversion_func an, um die Zeile in einen Datensatz umzuwandeln und fügt diesen der records-Liste hinzu. Schließlich gibt sie die Liste der Datensätze zurück.

Jetzt benötigen wir eine einfache Konvertierungsfunktion, um unsere convert_csv()-Funktion zu testen. Diese Funktion nimmt die Spaltenüberschriften und eine Zeile und wandelt die Zeile in ein Dictionary um, wobei die Spaltenüberschriften als Schlüssel verwendet werden.

Hier ist der Code für die make_dict()-Funktion. Fügen Sie diese Funktion ebenfalls zu Ihrer reader.py-Datei hinzu:

def make_dict(headers, row):
    '''
    Convert a row to a dictionary using the provided headers
    '''
    return dict(zip(headers, row))

Die make_dict()-Funktion verwendet die zip()-Funktion, um jede Spaltenüberschrift mit ihrem entsprechenden Wert in der Zeile zu verknüpfen und erstellt dann ein Dictionary aus diesen Paaren.

Lassen Sie uns diese Funktionen testen. Öffnen Sie eine Python-Shell, indem Sie die folgenden Befehle im Terminal ausführen:

cd ~/project
python3 -i reader.py

Die -i-Option im python3-Befehl startet den Python-Interpreter im interaktiven Modus und importiert die reader.py-Datei, sodass wir die Funktionen verwenden können, die wir gerade definiert haben.

In der Python-Shell führen Sie den folgenden Code aus, um unsere Funktionen zu testen:

## Open the CSV file
lines = open('portfolio.csv')

## Convert to a list of dictionaries using our new function
result = convert_csv(lines, make_dict)

## Print the result
print(result)

Dieser Code öffnet die portfolio.csv-Datei, verwendet die convert_csv()-Funktion mit der make_dict()-Konvertierungsfunktion, um die CSV-Daten in eine Liste von Dictionaries umzuwandeln, und gibt dann das Ergebnis aus.

Sie sollten eine Ausgabe ähnlich der folgenden sehen:

[{'name': 'AA', 'shares': '100', 'price': '32.20'}, {'name': 'IBM', 'shares': '50', 'price': '91.10'}, {'name': 'CAT', 'shares': '150', 'price': '83.44'}, {'name': 'MSFT', 'shares': '200', 'price': '51.23'}, {'name': 'GE', 'shares': '95', 'price': '40.37'}, {'name': 'MSFT', 'shares': '50', 'price': '65.10'}, {'name': 'IBM', 'shares': '100', 'price': '70.44'}]

Diese Ausgabe zeigt, dass unsere höhere Funktion convert_csv() korrekt funktioniert. Wir haben erfolgreich eine Funktion erstellt, die eine andere Funktion als Argument nimmt, was es uns ermöglicht, leicht zu ändern, wie die CSV-Daten konvertiert werden.

Um die Python-Shell zu verlassen, können Sie exit() eingeben oder Strg+D drücken.

Refactoring bestehender Funktionen

Jetzt haben wir eine höhere Funktion (Higher-Order Function) namens convert_csv() erstellt. Höhere Funktionen sind Funktionen, die andere Funktionen als Argumente nehmen oder Funktionen als Ergebnisse zurückgeben können. Sie sind ein mächtiges Konzept in Python, das uns helfen kann, modulareren und wiederverwendbaren Code zu schreiben. In diesem Abschnitt werden wir diese höhere Funktion nutzen, um die ursprünglichen Funktionen csv_as_dicts() und csv_as_instances() zu refaktorisieren. Refactoring ist der Prozess, bestehenden Code zu strukturieren, ohne sein externes Verhalten zu ändern, um seine interne Struktur zu verbessern, wie z. B. die Eliminierung von Code-Duplizierung.

Beginnen wir damit, die Datei reader.py im WebIDE zu öffnen. Wir werden die Funktionen wie folgt aktualisieren:

  1. Zunächst ersetzen wir die Funktion csv_as_dicts(). Diese Funktion wird verwendet, um Zeilen von CSV-Daten in eine Liste von Dictionaries umzuwandeln. Hier ist der neue Code:
def csv_as_dicts(lines, types, *, headers=None):
    '''
    Convert lines of CSV data into a list of dictionaries
    '''
    def dict_converter(headers, row):
        return {name: func(val) for name, func, val in zip(headers, types, row)}

    return convert_csv(lines, dict_converter, headers=headers)

In diesem Code definieren wir eine innere Funktion dict_converter, die headers und row als Argumente nimmt. Sie verwendet eine Dictionary-Comprehension, um ein Dictionary zu erstellen, bei dem die Schlüssel die Spaltenüberschriften sind und die Werte das Ergebnis der Anwendung der entsprechenden Typkonvertierungsfunktion auf die Werte in der Zeile sind. Dann rufen wir die Funktion convert_csv() mit der Funktion dict_converter als Argument auf.

  1. Als Nächstes ersetzen wir die Funktion csv_as_instances(). Diese Funktion wird verwendet, um Zeilen von CSV-Daten in eine Liste von Instanzen einer gegebenen Klasse umzuwandeln. Hier ist der neue Code:
def csv_as_instances(lines, cls, *, headers=None):
    '''
    Convert lines of CSV data into a list of instances
    '''
    def instance_converter(headers, row):
        return cls.from_row(row)

    return convert_csv(lines, instance_converter, headers=headers)

In diesem Code definieren wir eine innere Funktion instance_converter, die headers und row als Argumente nimmt. Sie ruft die Klassenmethode from_row der gegebenen Klasse cls auf, um eine Instanz aus der Zeile zu erstellen. Dann rufen wir die Funktion convert_csv() mit der Funktion instance_converter als Argument auf.

Nachdem wir diese Funktionen refaktoriert haben, müssen wir sie testen, um sicherzustellen, dass sie weiterhin wie erwartet funktionieren. Dazu führen wir die folgenden Befehle in einer Python-Shell aus:

cd ~/project
python3 -i reader.py

Der Befehl cd ~/project wechselt das aktuelle Arbeitsverzeichnis in das project-Verzeichnis. Der Befehl python3 -i reader.py führt die Datei reader.py im interaktiven Modus aus, was bedeutet, dass wir weiterhin Python-Code ausführen können, nachdem die Datei ausgeführt wurde.

Sobald die Python-Shell geöffnet ist, führen wir den folgenden Code aus, um die refaktorierten Funktionen zu testen:

## Define a simple Stock class for testing
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    @classmethod
    def from_row(cls, row):
        return cls(row[0], int(row[1]), float(row[2]))

    def __repr__(self):
        return f'Stock({self.name}, {self.shares}, {self.price})'

## Test csv_as_dicts
with open('portfolio.csv') as f:
    portfolio_dicts = csv_as_dicts(f, [str, int, float])
print("First dictionary:", portfolio_dicts[0])

## Test csv_as_instances
with open('portfolio.csv') as f:
    portfolio_instances = csv_as_instances(f, Stock)
print("First instance:", portfolio_instances[0])

In diesem Code definieren wir zunächst eine einfache Klasse Stock zum Testen. Die Methode __init__ initialisiert die Attribute einer Stock-Instanz. Die Klassenmethode from_row erstellt eine Stock-Instanz aus einer Zeile von CSV-Daten. Die Methode __repr__ liefert eine String-Repräsentation der Stock-Instanz.

Dann testen wir die Funktion csv_as_dicts(), indem wir die Datei portfolio.csv öffnen und sie zusammen mit einer Liste von Typkonvertierungsfunktionen an die Funktion übergeben. Wir geben das erste Dictionary in der resultierenden Liste aus.

Schließlich testen wir die Funktion csv_as_instances(), indem wir die Datei portfolio.csv öffnen und sie zusammen mit der Klasse Stock an die Funktion übergeben. Wir geben die erste Instanz in der resultierenden Liste aus.

Wenn alles korrekt funktioniert, sollten Sie eine Ausgabe ähnlich der folgenden sehen:

First dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
First instance: Stock(AA, 100, 32.2)

Diese Ausgabe zeigt, dass unsere refaktorierten Funktionen korrekt funktionieren. Wir haben die Code-Duplizierung erfolgreich eliminiert, während wir die gleiche Funktionalität beibehalten haben.

Um die Python-Shell zu verlassen, können Sie exit() eingeben oder Strg+D drücken.

Verwendung der map()-Funktion

In Python ist eine höhere Funktion (Higher-Order Function) eine Funktion, die eine andere Funktion als Argument nehmen oder eine Funktion als Ergebnis zurückgeben kann. Python's map()-Funktion ist ein hervorragendes Beispiel für eine höhere Funktion. Sie ist ein mächtiges Werkzeug, das es Ihnen ermöglicht, eine gegebene Funktion auf jedes Element in einem iterierbaren Objekt, wie z. B. einer Liste oder einem Tupel, anzuwenden. Nachdem die Funktion auf jedes Element angewendet wurde, gibt sie einen Iterator der Ergebnisse zurück. Diese Eigenschaft macht map() perfekt für die Verarbeitung von Datenfolgen, wie z. B. Zeilen in einer CSV-Datei.

Die grundlegende Syntax der map()-Funktion lautet wie folgt:

map(function, iterable, ...)

Hierbei ist die function die Operation, die Sie auf jedes Element in der iterable ausführen möchten. Die iterable ist eine Sequenz von Elementen, wie z. B. eine Liste oder ein Tupel.

Schauen wir uns ein einfaches Beispiel an. Angenommen, Sie haben eine Liste von Zahlen und möchten jede Zahl in dieser Liste quadrieren. Sie können die map()-Funktion verwenden, um dies zu erreichen. So geht's:

numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x * x, numbers))
print(squared)  ## Output: [1, 4, 9, 16, 25]

In diesem Beispiel definieren wir zunächst eine Liste namens numbers. Dann verwenden wir die map()-Funktion. Die lambda-Funktion lambda x: x * x ist die Operation, die wir auf jedes Element in der numbers-Liste ausführen möchten. Die map()-Funktion wendet diese lambda-Funktion auf jede Zahl in der Liste an. Da map() einen Iterator zurückgibt, wandeln wir ihn mit der list()-Funktion in eine Liste um. Schließlich geben wir die squared-Liste aus, die die quadrierten Werte der ursprünglichen Zahlen enthält.

Jetzt schauen wir uns an, wie wir die map()-Funktion verwenden können, um unsere convert_csv()-Funktion zu modifizieren. Zuvor haben wir eine for-Schleife verwendet, um über die Zeilen in den CSV-Daten zu iterieren. Jetzt ersetzen wir diese for-Schleife durch die map()-Funktion.

def convert_csv(lines, conversion_func, *, headers=None):
    '''
    Convert lines of CSV data using the provided conversion function
    '''
    rows = csv.reader(lines)
    if headers is None:
        headers = next(rows)

    ## Use map to apply conversion_func to each row
    records = list(map(lambda row: conversion_func(headers, row), rows))
    return records

Diese aktualisierte Version der convert_csv()-Funktion macht genau dasselbe wie die vorherige Version, aber sie verwendet die map()-Funktion anstelle einer for-Schleife. Die lambda-Funktion innerhalb der map() nimmt jede Zeile aus den CSV-Daten und wendet die conversion_func zusammen mit den Spaltenüberschriften darauf an.

Lassen Sie uns diese aktualisierte Funktion testen, um sicherzustellen, dass sie korrekt funktioniert. Öffnen Sie zunächst Ihr Terminal und navigieren Sie in das Projektverzeichnis. Starten Sie dann die interaktive Python-Shell mit der reader.py-Datei.

cd ~/project
python3 -i reader.py

Sobald Sie sich in der Python-Shell befinden, führen Sie den folgenden Code aus, um die aktualisierte convert_csv()-Funktion zu testen:

## Test the updated convert_csv function
with open('portfolio.csv') as f:
    result = convert_csv(f, make_dict)
print(result[0])  ## Should print the first dictionary

## Test that csv_as_dicts still works
with open('portfolio.csv') as f:
    portfolio = csv_as_dicts(f, [str, int, float])
print(portfolio[0])  ## Should print the first dictionary with converted types

Nachdem Sie diesen Code ausgeführt haben, sollten Sie eine Ausgabe ähnlich der folgenden sehen:

{'name': 'AA', 'shares': '100', 'price': '32.20'}
{'name': 'AA', 'shares': 100, 'price': 32.2}

Diese Ausgabe zeigt, dass die aktualisierte convert_csv()-Funktion, die die map()-Funktion verwendet, korrekt funktioniert, und die Funktionen, die darauf basieren, weiterhin wie erwartet funktionieren.

Die Verwendung der map()-Funktion hat mehrere Vorteile:

  1. Sie kann kompakter sein als eine for-Schleife. Anstatt mehrere Codezeilen für eine for-Schleife zu schreiben, können Sie dasselbe Ergebnis mit einer einzigen Zeile mit map() erreichen.
  2. Sie kommuniziert klar, dass Sie jedes Element in einer Sequenz transformieren möchten. Wenn Sie map() sehen, wissen Sie sofort, dass Sie eine Funktion auf jedes Element in einem iterierbaren Objekt anwenden.
  3. Sie kann speichereffizienter sein, da sie einen Iterator zurückgibt. Ein Iterator generiert Werte on-the-fly, was bedeutet, dass er nicht alle Ergebnisse auf einmal im Speicher speichert. In unserem Beispiel haben wir den von map() zurückgegebenen Iterator in eine Liste umgewandelt, aber in einigen Fällen können Sie direkt mit dem Iterator arbeiten, um Speicher zu sparen.

Um die Python-Shell zu verlassen, können Sie exit() eingeben oder Strg+D drücken.

Zusammenfassung

In diesem Lab haben Sie sich mit höheren Funktionen (Higher-Order Functions) in Python vertraut gemacht und gelernt, wie sie dazu beitragen, modulareren und wartbareren Code zu schreiben. Zunächst haben Sie Code-Duplizierung in zwei ähnlichen Funktionen identifiziert. Dann haben Sie eine höhere Funktion convert_csv() erstellt, die eine Konvertierungsfunktion als Argument akzeptiert, und die ursprünglichen Funktionen so refaktoriert, dass sie diese nutzen. Schließlich haben Sie die höhere Funktion aktualisiert, um Python's integrierte map()-Funktion zu verwenden.

Diese Techniken sind wertvolle Werkzeuge in einem Python-Programmierer's Arsenal. Höhere Funktionen fördern die Wiederverwendung von Code und die Trennung von Zuständigkeiten, während das Übergeben von Funktionen als Argumente für flexibleres und anpassbareres Verhalten sorgt. Funktionen wie map() bieten knappe Möglichkeiten, Daten zu transformieren. Das Beherrschen dieser Konzepte ermöglicht es Ihnen, Python-Code zu schreiben, der kompakter, wartbarer und fehleranfälliger ist.