Gültigkeitsbereich und veränderliche Standardwerte
Wo ein Name wohnt und wer ihn sehen kann, der Unterschied zwischen Lesen und Schreiben eines äußeren Namens, UnboundLocalError, und Pythons berühmteste Falle — die leere Liste als Standardwert.
Wie möchtest du lernen?
Das Problem, das wir lösen
Die „Wenn etwas schiefgeht“-Liste aus Kapitel neunzehn ließ eine Zeile offen: „Eine Variable innerhalb der Funktion ist von außen nicht sichtbar — das ist erwartet, und warum Kapitel einundzwanzig das Thema ist.“
Dies ist dieses Kapitel.
def add_tax(price):
tax = price * 0.15
return price + tax
print(add_tax(100))
print(tax)115.0
NameError: name 'tax' is not defined. Did you mean: 'max'?Die erste Zeile funktionierte, also muss tax existiert haben. Und außerhalb existiert es nicht. (Vor der Fehlerzeile druckt Python auch einige Traceback-Zeilen; in diesem Kapitel wird nur die letzte Zeile gezeigt.)
Das ist Schutz statt Einschränkung. In einem Programm mit fünfzig Funktionen würden, wenn jeder interne Name nach außen sickern würde, die total-Variablen zweier Funktionen einander überschreiben — und niemand würde es bemerken. Namen innerhalb einer Funktion bleiben drinnen, sodass das Schreiben einer Funktion nicht erfordert, die Namen in den anderen neunundvierzig zu kennen.
Dieses Kapitel behandelt diese Regel und eine ihrer Folgen, die Pythons berühmteste Falle ist.
Am Ende dieses Kapitels kannst du
- Sagen, wo ein Name „wohnt“ und wer ihn sehen kann
- Einen äußeren Namen von innerhalb einer Funktion lesen und sagen, warum du keinen schreiben kannst
- Einen
UnboundLocalErrorlesen und sagen, was passiert ist - Erklären, warum eine an eine Funktion übergebene Liste verändert zurückkommen kann
- Pythons berühmteste Falle erkennen und vermeiden: den veränderlichen Standardwert
Voraussetzungen: Funktionsargumente.
Drinnen bleibt drinnen
Jeder Name, der innerhalb einer Funktion erstellt wird — Parameter eingeschlossen — lebt nur für diesen Aufruf. Wenn die Funktion endet, sind die Namen weg.
Das nennt man Gültigkeitsbereich (Scope): den Bereich, in dem ein Name bekannt ist.
Einen äußeren Namen lesen funktioniert
RATE = 0.15
def add_tax(price):
return price + price * RATE
print(add_tax(100))115.0Es gibt kein RATE innerhalb der Funktion, also schaut Python nach außen und findet eins. Einen äußeren Namen von innen zu lesen ist in Ordnung.
Der Großbuchstaben-Name ist eine Konvention, keine Python-Regel — er sagt einem Leser „das ist eine Konstante, sie ändert sich nicht“. So verwendet ist ein äußerer Name am sichersten.
Aber einen schreiben geht nicht
count = 10
def bump():
count = 99
return count
print(bump())
print(count)99
10Das innere count = 99 hat das äußere count nie berührt. Es hat einen neuen, lokalen Namen erstellt, der nur innerhalb der Funktion lebt und den äußeren für die Dauer verdeckt.
Das ist die Regel, und sie ist Absicht: eine Funktion kann nicht versehentlich etwas von außen ändern.
Und daraus entsteht ein merkwürdig aussehender Fehler:
count = 10
def bump():
count = count + 1
return count
print(bump())UnboundLocalError: cannot access local variable 'count' where it is not associated with a valueDie Zeile liest sich so, als würde sie das äußere count nehmen und eins addieren. Aber Python prüft die ganze Funktion bevor sie läuft, sieht, dass etwas count zuweist, und entscheidet, dass count von Anfang bis Ende lokal für diese Funktion ist.
Dann, beim Auswerten von count + 1 auf der rechten Seite, sucht es nach dem lokalen count, der noch keinen Wert erhalten hat — und hält an.
Die Meldung ist daher ungewöhnlich genau: der Name ist lokal, und ihm ist noch nichts zugeordnet worden.
Die Lösung ist fast immer dieselbe, und es ist nicht global: nimm den Wert als Argument entgegen und returne das Ergebnis.
def bump(count):
return count + 1
count = bump(count)Python hat zwar ein global-Schlüsselwort, und dieser Kurs lehrt es nicht, weil das Problem, das Leute damit lösen wollen, eine bessere Antwort hat. Eine Funktion, die äußeren Zustand ändert, kann man vom Aufrufort aus nicht verstehen — und das ist die Definition von Code, der schwer zu testen und schwer zu vertrauen ist.
Aber das Ding, das du übergeben hast, kann sich ändern
Der Gültigkeitsbereich schützt Namen, nicht Objekte. Der Unterschied ist subtil und wichtig.
def add_item(items):
items.append("pen")
return items
basket = []
add_item(basket)
add_item(basket)
print(basket)['pen', 'pen']Das äußere basket hat sich geändert, obwohl kein return gespeichert wurde.
Der Grund ist der aus Kapitel zwölf: der Name items ist neu, aber er zeigt auf dieselbe Liste. append ändert diese Liste, und beide Namen zeigen die Änderung.
Vergleiche:
def replace(items):
items = ["new"]
return items
basket = ["old"]
result = replace(basket)
print(basket)
print(result)['old']
['new']Hier hat items = [...] einen neuen Wert zugewiesen, sodass der lokale Name auf eine andere Liste zeigt. Das äußere basket ist, wo es war.
Die Regel in einer Zeile: das Objekt zu ändern (append, sort, [i] =) ist von außen sichtbar; den Namen auf ein anderes Objekt zeigen zu lassen (=) nicht.
Pythons berühmteste Falle
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("pen"))
print(add_item("bag"))
print(add_item("ink"))['pen']
['pen', 'bag']
['pen', 'bag', 'ink']Drei getrennte Aufrufe, und jeder behält, was die vorherigen hinterlassen haben.
Versteh den Grund, und du vergisst ihn nie: der Standardwert wird einmal ausgewertet, wenn die def-Zeile läuft — nicht wenn die Funktion aufgerufen wird. Diese leere Liste ist eine einzige Liste, die an die Funktion angehängt ist, und jeder Aufruf teilt sie.
Die Lösung ist konventionell und einfach:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item("pen"))
print(add_item("bag"))['pen']
['bag']None ist unveränderlich, also kostet das Teilen nichts, und die neue Liste wird bei jedem Aufruf frisch erstellt.
Als Regel: schreib niemals eine Liste, ein Dictionary oder ein Set als Standardwert. Zahlen, Text, True/False und None sind sicher, genau weil sie nicht geändert werden können.
Warumis Noneund nicht== None?isfragt „ist das genau dasselbe Objekt?“, und es gibt nur einNoneim ganzen Programm.==prüft Gleichheit, die manche Typen auf überraschende Weise definieren. FürNoneistissowohl konventionell als auch verlässlich.
Ein vollständiges Beispiel
basket.py:
# Where a name lives decides who can see it and who can change it
TAX_RATE = 0.15
def line_total(price, quantity):
"""Pure: takes values, returns a value, touches nothing outside."""
return round(price * quantity * (1 + TAX_RATE), 2)
def add_line(basket, name, price, quantity):
"""Impure on purpose: it changes the basket it was handed."""
basket.append({"name": name, "total": line_total(price, quantity)})
return basket
def summarise(basket, note=None):
"""`None` as the default, so no list or dict is shared between calls."""
notes = [] if note is None else [note]
total = sum(line["total"] for line in basket)
notes.append(f"{len(basket)} lines, {total:.2f} in total")
return notes
shopping = []
add_line(shopping, "pen", 15.0, 3)
add_line(shopping, "bag", 850.0, 1)
for line in shopping:
print(f"{line['name']:<6} {line['total']:>8.2f}")
print()
for note in summarise(shopping, "morning order"):
print(note)
print()
print("basket outside the function:", len(shopping), "lines")pen 51.75
bag 977.50
morning order
2 lines, 1029.25 in total
basket outside the function: 2 linesDrei Funktionen mit drei verschiedenen Beziehungen zur Welt außerhalb, und das ist das, worauf es ankommt.
line_total berührt nichts. Sie nimmt Werte und gibt einen Wert zurück. Sie liest TAX_RATE und ändert es nie. Eine solche Funktion nennt man rein — dieselbe Eingabe gibt immer dieselbe Ausgabe, ohne Seiteneffekte irgendwo. Sie ist die einfachste Art zu testen und die schwerste, falsch zu machen.
add_line ändert absichtlich etwas von außen. Sie erhält eine Liste und fügt etwas hinzu — und die letzte Zeile beweist, dass das äußere shopping wirklich zwei Zeilen hält. Daran ist nichts falsch, solange der Name es sagt. add_line zu lesen verrät, dass etwas hinzugefügt wird.
summarise nutzt None als Standardwert. Wäre es note=[] geschrieben, würde jeder Aufruf dieselbe Liste teilen, und der zweite Bericht trüge die Notiz des ersten.
Wenn etwas schiefgeht
NameError: name 'tax' is not defined Ein Name aus einer Funktion wurde außen benutzt. returne, was außen gebraucht wird.
UnboundLocalError: cannot access local variable ... where it is not associated with a value Einem Namen wird innerhalb der Funktion etwas zugewiesen und er wird vor dieser Zuweisung gelesen. Python entschied, er sei lokal. Nimm den Wert als Argument entgegen und returne das Ergebnis.
Meine Liste hat sich geändert, nachdem ich sie an eine Funktion übergeben habe Das ist das erwartete Verhalten — der Name ist neu, die Liste dieselbe. Um es zu vermeiden, schreib items = items.copy() als erste Zeile der Funktion, oder baue eine neue Liste und returne sie.
Die Funktion behält ihre vorherigen Ergebnisse bei Ein veränderlicher Standardwert. Nicht def f(x, acc=[]) sondern def f(x, acc=None), mit if acc is None: acc = [] drinnen.
Ich habe denselben Namen in zwei Funktionen benutzt — ist das ein Problem? Es ist keins, und das ist der ganze Zweck des Gültigkeitsbereichs. Die total-Variablen zweier Funktionen sind völlig getrennt.
Schritt 4 von 6 — Vorhersagen
Prüfe dein Verständnis
count wird innerhalb der Funktion ein Wert zugewiesen. Was drucken die beiden Zeilen?
count = 10
def bump():
count = 99
return count
print(bump())
print(count)- A10 10
- B99 10
- CEin `UnboundLocalError`
- D99 99
Es gibt kein return, doch die äußere Liste wird gedruckt. Was erscheint?
def add_item(items):
items.append("pen")
basket = []
add_item(basket)
add_item(basket)
print(basket)- A[]
- B['pen']
- CNone
- D['pen', 'pen']
Zwei getrennte Aufrufe, mit einer leeren Liste als Standardwert. Was wird gedruckt?
def collect(item, box=[]):
box.append(item)
return box
print(collect("pen"))
print(collect("bag"))- A['pen', 'bag'] ['pen', 'bag']
- B['pen'] ['bag']
- C['pen'] ['pen', 'bag']
- D['pen'] ['pen']
Zum Antworten brauchst du ein Konto
Melde dich an, um deine Antworten zu prüfen
Die Fragen stehen oben, und sie im Kopf durchzuarbeiten ist der wichtige Teil. Melde dich an, um die Antworten, die Erklärungen und die dreistufigen Hinweise zu sehen.
Jetzt bist du dran
Schreib eine Datei namens cart.py mit drei Funktionen:
line_total(price, quantity, tax_rate=0.15)— rein, berührt nichtsadd_to(cart, name, price, quantity)— fügt zum übergebenen cart hinzureceipt(cart, header=None)— gibt eine Liste von Zeilen zurück, erfindet einen Header, wenn keiner gegeben ist
Baue dann einen leeren cart, füge ein paar Dinge hinzu, und druck den receipt.
Zum Schluss drei Experimente, die alle wert sind, mit eigenen Augen gesehen zu werden:
- Ändere
receiptsStandardwert zuheader=[], ruf die Funktion zweimal auf und druck beide Ergebnisse. Was siehst du beim zweiten Mal? - Füge
cart = cart.copy()als erste Zeile vonadd_tohinzu, dann druck den äußeren cart. Wie viele Dinge sind drin, und warum? - Weise
TAX_RATE = 0.2innerhalb einer Funktion zu, dann druckTAX_RATEaußen. Welchen Wert siehst du?
Die drei Antworten zusammen sind das ganze Kapitel: welche Namen wo wohnen, und welche Änderungen nach außen reichen.
Schritt 6 von 6
Herausforderung — das Kapitel-Quiz
Zehn Fragen von leicht bis schwer. Die letzten sind absichtlich knifflig.
Melde dich an, um das Quiz zu machen