Mehr über Funktionen

PythonPythonIntermediate
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

Obwohl Funktionen bereits früher eingeführt wurden, wurden nur sehr wenige Details darüber gegeben, wie sie tatsächlich auf einer tieferen Ebene funktionieren. Dieser Abschnitt zielt darauf ab, einige Lücken zu schließen und Themen wie Aufrufkonventionen, Gültigkeitsbereichsregeln und mehr zu diskutieren.

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 Fortgeschrittener mit einer Abschlussquote von 75% ist. Es hat eine positive Bewertungsrate von 100% von den Lernenden erhalten.

Ein Funktionsaufruf durchführen

Betrachten Sie diese Funktion:

def read_prices(filename, debug):
  ...

Sie können die Funktion mit Positionsargumenten aufrufen:

prices = read_prices('prices.csv', True)

Oder Sie können die Funktion mit Schlüsselwortargumenten aufrufen:

prices = read_prices(filename='prices.csv', debug=True)

Standardargumente

Manchmal möchten Sie, dass ein Argument optional ist. Wenn so, weisen Sie im Funktionsdefinition einen Standardwert zu.

def read_prices(filename, debug=False):
 ...

Wenn ein Standardwert zugewiesen ist, ist das Argument in Funktionsaufrufen optional.

d = read_prices('prices.csv')
e = read_prices('prices.dat', True)

Hinweis: Argumente mit Standardwerten müssen am Ende der Argumentliste stehen (alle nicht-optionalen Argumente kommen zuerst).

Verwenden Sie für optionale Argumente lieber Schlüsselwortargumente

Vergleichen Sie diese beiden unterschiedlichen Aufrufstile:

parse_data(data, False, True) #?????

parse_data(data, ignore_errors=True)
parse_data(data, debug=True)
parse_data(data, debug=True, ignore_errors=True)

In den meisten Fällen verbessern Schlüsselwortargumente die Klarheit des Codes - insbesondere für Argumente, die als Flags dienen oder die mit optionalen Funktionen zusammenhängen.

Design-Best Practices

Geben Sie immer kurze, aber aussagekräftige Namen für Funktionsargumente.

Jemand, der eine Funktion verwendet, kann den Schlüsselwort-Aufrufstil verwenden.

d = read_prices('prices.csv', debug=True)

Python-Entwicklungstools werden die Namen in Hilfefunktionen und -dokumentationen anzeigen.

Rückgabe von Werten

Die return-Anweisung gibt einen Wert zurück

def square(x):
    return x * x

Wenn kein Rückgabewert angegeben wird oder return fehlt, wird None zurückgegeben.

def bar(x):
    statements
    return

a = bar(4)      ## a = None

## ODER
def foo(x):
    statements  ## Keine `return`

b = foo(4)      ## b = None

Mehrere Rückgabewerte

Funktionen können nur einen Wert zurückgeben. Ein Funktionsaufruf kann jedoch mehrere Werte in einem Tupel zurückgeben.

def divide(a,b):
    q = a // b      ## Quotient
    r = a % b       ## Rest
    return q, r     ## Gebe ein Tupel zurück

Verwendungsbeispiel:

x, y = divide(37,5) ## x = 7, y = 2

x = divide(37, 5)   ## x = (7, 2)

Variablenbereich

Programme weisen Variablen Werte zu.

x = value ## Globale Variable

def foo():
    y = value ## Lokale Variable

Variablenzuweisungen erfolgen außerhalb und innerhalb von Funktionsdefinitionen. Variablen, die außerhalb definiert werden, sind „global“. Variablen innerhalb einer Funktion sind „lokal“.

Lokale Variablen

Variablen, die innerhalb von Funktionen zugewiesen werden, sind privat.

def read_portfolio(filename):
    portfolio = []
    for line in open(filename):
        fields = line.split(',')
        s = (fields[0], int(fields[1]), float(fields[2]))
        portfolio.append(s)
    return portfolio

In diesem Beispiel sind filename, portfolio, line, fields und s lokale Variablen. Diese Variablen werden nicht behalten oder zugänglich gemacht, nachdem der Funktionsaufruf beendet ist.

>>> stocks = read_portfolio('portfolio.csv')
>>> fields
Traceback (most recent call last):
File "<stdin>", line 1, in?
NameError: name 'fields' is not defined
>>>

Lokale Variablen können auch nicht mit Variablen kollidieren, die an anderen Stellen definiert sind.

Globale Variablen

Funktionen können frei auf die Werte von globalen Variablen zugreifen, die in der selben Datei definiert sind.

name = 'Dave'

def greeting():
    print('Hello', name)  ## Verwenden der globalen Variable `name`

Funktionen können globale Variablen jedoch nicht verändern:

name = 'Dave'

def spam():
  name = 'Guido'

spam()
print(name) ## Gibt 'Dave' aus

Denken Sie daran: Alle Zuweisungen in Funktionen sind lokal.

Ändern von Globalen Variablen

Wenn Sie eine globale Variable ändern müssen, müssen Sie sie als solche deklarieren.

name = 'Dave'

def spam():
    global name
    name = 'Guido' ## Ändert die globale Variable oben

Die global-Deklaration muss vor ihrem Gebrauch erscheinen und die entsprechende Variable muss in derselben Datei wie die Funktion existieren. Nachdem Sie das gesehen haben, wissen Sie, dass dies als schlechte Form angesehen wird. Tatsächlich sollten Sie global gänzlich vermeiden, wenn möglich. Wenn Sie eine Funktion benötigen, um einen bestimmten Zustand außerhalb der Funktion zu ändern, ist es besser, eine Klasse zu verwenden (mehr dazu später).

Argumentübergabe

Wenn Sie eine Funktion aufrufen, sind die Argumentvariablen Namen, die auf die übergebenen Werte verweisen. Diese Werte sind KEINE Kopien. Wenn veränderbare Datentypen übergeben werden (z.B. Listen, Dictonaries), können sie in-place geändert werden.

def foo(items):
    items.append(42)    ## Modifiziert das Eingabeelement

a = [1, 2, 3]
foo(a)
print(a)                ## [1, 2, 3, 42]

Wichtiger Punkt: Funktionen erhalten keine Kopie der Eingabeargumente.

Neuzuweisung vs Änderung

Stellen Sie sicher, dass Sie den subtilen Unterschied zwischen der Änderung eines Werts und der Neuzuweisung eines Variablennamens verstehen.

def foo(items):
    items.append(42)    ## Ändert das Eingabeelement

a = [1, 2, 3]
foo(a)
print(a)                ## [1, 2, 3, 42]

## VS
def bar(items):
    items = [4,5,6]    ## Ändert die lokale Variable `items`, sodass sie auf ein anderes Objekt zeigt

b = [1, 2, 3]
bar(b)
print(b)                ## [1, 2, 3]

Hinweis: Die Variablennzuweisung überschreibt niemals das Speicher. Der Name wird lediglich an einen neuen Wert gebunden.

In dieser Reihe von Übungen implementieren Sie möglicherweise den mächtigsten und schwierigsten Teil des Kurses. Es gibt viele Schritte und viele Konzepte aus früheren Übungen werden auf einmal zusammengeführt. Die endgültige Lösung umfasst nur etwa 25 Zeilen Code, aber nehmen Sie sich Ihren Zeit und stellen Sie sicher, dass Sie jeden Teil verstehen.

Ein zentraler Teil Ihres report.py-Programms konzentriert sich auf das Lesen von CSV-Dateien. Beispielsweise liest die Funktion read_portfolio() eine Datei, die Zeilen mit Portfolio-Daten enthält, und die Funktion read_prices() liest eine Datei, die Zeilen mit Preisdaten enthält. In beiden Funktionen gibt es viele niedrigere "umständliche" Teile und ähnliche Merkmale. Beispielsweise öffnen sie beide eine Datei und umschließen sie mit dem csv-Modul und sie konvertieren verschiedene Felder in neue Typen.

Wenn Sie viel Datei-Parsing im echten Leben machen würden, würden Sie wahrscheinlich einige dieser Dinge aufräumen und es allgemeiner einsetzbar machen wollen. Das ist unser Ziel.

Beginnen Sie diese Übung, indem Sie die Datei fileparse.py öffnen. Hier werden wir unsere Arbeit verrichten.

Übung 3.3: Lesen von CSV-Dateien

Zunächst konzentrieren wir uns nur auf das Problem, eine CSV-Datei in eine Liste von Dictionaries zu lesen. In der Datei fileparse_3.3.py definieren Sie eine Funktion, die wie folgt aussieht:

## fileparse_3.3.py
import csv

def parse_csv(filename):
    '''
    Parse a CSV file into a list of records
    '''
    with open(filename) as f:
        rows = csv.reader(f)

        ## Read the file headers
        headers = next(rows)
        records = []
        for row in rows:
            if not row:    ## Skip rows with no data
                continue
            record = dict(zip(headers, row))
            records.append(record)

    return records

Diese Funktion liest eine CSV-Datei in eine Liste von Dictionaries und versteckt dabei die Details des Dateiöffnens, der Umschließung mit dem csv-Modul, des Überspringens von leeren Zeilen usw.

Testen Sie es:

Hinweis: python3 -i fileparse_3.3.py.

>>> portfolio = parse_csv('/home/labex/project/portfolio.csv')
>>> portfolio
[{'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'}]
>>>

Dies ist gut, nur dass Sie keine nützlichen Berechnungen mit den Daten durchführen können, da alles als String dargestellt ist. Wir werden das bald beheben, aber lassen Sie uns zunächst weiterbauen.

✨ Lösung prüfen und üben

Übung 3.4: Erstellen eines Spaltenauswahlers

In vielen Fällen interessieren Sie sich nur für ausgewählte Spalten aus einer CSV-Datei, nicht für alle Daten. Ändern Sie die parse_csv()-Funktion so, dass sie optional erlaubt, dass Benutzerangegebene Spalten ausgewählt werden, wie folgt:

>>> ## Lese alle Daten
>>> portfolio = parse_csv('/home/labex/project/portfolio.csv')
>>> portfolio
[{'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'}]

>>> ## Lese nur einige Daten
>>> shares_held = parse_csv('/home/labex/project/portfolio.csv', select=['name','shares'])
>>> shares_held
[{'name': 'AA','shares': '100'}, {'name': 'IBM','shares': '50'}, {'name': 'CAT','shares': '150'}, {'name': 'MSFT','shares': '200'}, {'name': 'GE','shares': '95'}, {'name': 'MSFT','shares': '50'}, {'name': 'IBM','shares': '100'}]
>>>

Ein Beispiel für einen Spaltenauswähler wurde in Übung 2.23 gegeben. Hier ist jedoch eine Möglichkeit, dies zu tun:

## fileparse_3.4.py
import csv

def parse_csv(filename, select=None):
    '''
    Parse a CSV file into a list of records
    '''
    with open(filename) as f:
        rows = csv.reader(f)

        ## Lese die Dateiheader
        headers = next(rows)

        ## Wenn ein Spaltenauswähler angegeben wurde, finde die Indizes der angegebenen Spalten.
        ## Vereng auch die Menge der Header, die für die resultierenden Dictionaries verwendet werden
        if select:
            indices = [headers.index(colname) for colname in select]
            headers = select
        else:
            indices = []

        records = []
        for row in rows:
            if not row:    ## Überspringe Zeilen ohne Daten
                continue
            ## Filtere die Zeile, wenn spezifische Spalten ausgewählt wurden
            if indices:
                row = [ row[index] for index in indices ]

            ## Mache ein Dictionary
            record = dict(zip(headers, row))
            records.append(record)

    return records

Es gibt einige knifflige Punkte bei diesem Teil. Wahrscheinlich der wichtigste ist die Zuordnung der Spaltenauswahlen zu Zeilenindizes. Beispielsweise nehmen Sie an, die Eingabedatei hätte die folgenden Header:

>>> headers = ['name', 'date', 'time','shares', 'price']
>>>

Nun nehmen Sie an, die ausgewählten Spalten wären wie folgt:

>>> select = ['name','shares']
>>>

Um die richtige Auswahl durchzuführen, müssen Sie die ausgewählten Spaltennamen auf die Spaltenindizes in der Datei abbilden. Dies ist, was dieser Schritt tut:

>>> indices = [headers.index(colname) for colname in select ]
>>> indices
[0, 3]
>>>

Mit anderen Worten, "name" ist Spalte 0 und "shares" ist Spalte 3. Wenn Sie eine Zeile von Daten aus der Datei lesen, werden die Indizes verwendet, um sie zu filtern:

>>> row = ['AA', '6/11/2007', '9:50am', '100', '32.20' ]
>>> row = [ row[index] for index in indices ]
>>> row
['AA', '100']
>>>
✨ Lösung prüfen und üben

Übung 3.5: Ausführen von Typumwandlungen

Ändern Sie die parse_csv()-Funktion im Verzeichnis /home/labex/project/fileparse_3.5.py so, dass sie optional erlaubt, dass Typumwandlungen auf die zurückgegebenen Daten angewendet werden. Beispielsweise:

>>> portfolio = parse_csv('/home/labex/project/portfolio.csv', types=[str, int, float])
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'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.1}, {'name': 'IBM','shares': 100, 'price': 70.44}]

>>> shares_held = parse_csv('/home/labex/project/portfolio.csv', select=['name','shares'], types=[str, int])
>>> shares_held
[{'name': 'AA','shares': 100}, {'name': 'IBM','shares': 50}, {'name': 'CAT','shares': 150}, {'name': 'MSFT','shares': 200}, {'name': 'GE','shares': 95}, {'name': 'MSFT','shares': 50}, {'name': 'IBM','shares': 100}]
>>>

Sie haben dies bereits in Übung 2.24 untersucht. Sie müssen folgenden Codeausschnitt in Ihre Lösung einfügen:

...
if types:
    row = [func(val) for func, val in zip(types, row) ]
...
✨ Lösung prüfen und üben

Übung 3.6: Arbeiten ohne Header

Einige CSV-Dateien enthalten keine Header-Informationen. Beispielsweise sieht die Datei prices.csv so aus:

"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
...

Ändern Sie die parse_csv()-Funktion im Verzeichnis /home/labex/project/fileparse_3.6.py so, dass sie mit solchen Dateien umgehen kann, indem stattdessen eine Liste von Tupeln erstellt wird. Beispielsweise:

>>> prices = parse_csv('/home/labex/project/prices.csv', types=[str,float], has_headers=False)
>>> prices
[('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27), ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47), ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16), ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16), ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99), ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19), ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26), ('WMT', 49.74), ('XOM', 69.35)]
>>>

Um diese Änderung vorzunehmen, müssen Sie den Code so ändern, dass die erste Zeile der Daten nicht als Headerzeile interpretiert wird. Außerdem müssen Sie sicherstellen, dass Sie keine Dictionaries erstellen, da es keine Spaltennamen mehr gibt, die als Schlüssel verwendet werden können.

✨ Lösung prüfen und üben

Übung 3.7: Auswählen eines anderen Spaltentrennzeichens

Obwohl CSV-Dateien ziemlich üblich sind, ist es auch möglich, dass Sie eine Datei finden, die ein anderes Spaltentrennzeichen wie ein Tabulator oder ein Leerzeichen verwendet. Beispielsweise sieht die Datei portfolio.dat so aus:

name shares price
"AA" 100 32.20
"IBM" 50 91.10
"CAT" 150 83.44
"MSFT" 200 51.23
"GE" 95 40.37
"MSFT" 50 65.10
"IBM" 100 70.44

Die csv.reader()-Funktion erlaubt, ein anderes Spaltentrennzeichen anzugeben, wie folgt:

rows = csv.reader(f, delimiter=' ')

Ändern Sie Ihre parse_csv()-Funktion im Verzeichnis /home/labex/project/fileparse_3.7.py so, dass sie auch das Spaltentrennzeichen ändern lässt.

Beispielsweise:

>>> portfolio = parse_csv('/home/labex/project/portfolio.dat', types=[str, int, float], delimiter=' ')
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'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.1}, {'name': 'IBM','shares': 100, 'price': 70.44}]
>>>
✨ Lösung prüfen und üben

Kommentar

Wenn Sie so weit gekommen sind, haben Sie eine schöne Bibliotheksfunktion erstellt, die wirklich nützlich ist. Sie können sie verwenden, um beliebige CSV-Dateien zu analysieren, Spalten von Interesse auszuwählen, Typumwandlungen durchzuführen, ohne zu viel um die inneren Mechanismen von Dateien oder das csv-Modul zu kümmern.

Zusammenfassung

Herzlichen Glückwunsch! Sie haben das Lab zu Funktionen abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.