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.
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.
Ü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']
>>>
Ü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) ]
...
Ü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.
Ü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}]
>>>
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.