Aufwärmen · Aufgabe 1 von 7
// I2.1 · ca. 30 Min. · Aufbau
Gültigkeitsbereiche und Namensräume
Nach dieser Lektion sagen Sie vorher, welche Variable ein Name meint, ändern äußere Variablen mit global und nonlocal und beheben einen UnboundLocalError.
Lektion 1 von 6 in I2 Klassen
Danach können Sie
- Vorhersagen, welche Bindung ein Name meint, entlang der lokalen, umschließenden, globalen und eingebauten Scopes
- Einen Modulnamen mit global und einen umschließenden Namen mit nonlocal neu binden und erkennen, wann keins von beiden nötig ist
- Erklären, warum eine Zuweisung irgendwo in der Funktion einen Namen lokal macht, und den UnboundLocalError beheben
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: drei Funktionen, ein Name. Was gibt das aus?
x = "global" def outer(): x = "enclosing" def inner(): return x return inner() print(outer(), x)Üben · Aufgabe 3 von 7
Setzen Sie das Schlüsselwort ein, damit step() das count von make_counter ändert und jeder Aufruf die nächste Zahl liefert.
def make_counter(): count = 0 def step(): ____ count count += 1 return count return stepcountÜben · Aufgabe 4 von 7
Diesmal ohne global-Anweisung. Was gibt das aus?
names = [] def add(name): names.append(name) add("ada") add("bob") print(names)Üben · Aufgabe 5 von 7
Ordnen Sie jeder Zeile, die in einer Funktion steht, ihre Wirkung zu.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Das print steht vor der Zuweisung. Was passiert, wenn show() läuft?
total = 10 def show(): print(total) total = 20 show()Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie make_averager(), das eine Funktion add(value) zurückgibt. Jeder Aufruf von add liefert den Durchschnitt aller bisher übergebenen Werte: 10, dann 20, dann 60 ergeben 10.0, 15.0 und 30.0. Halten Sie Summe und Anzahl in make_averager, nicht in globalen Variablen.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Ein Debug-Schalter und Ticketzähler
Drei Wege, wie eine Funktion einen Namen außerhalb ihrer selbst erreicht. enable_debug und log binden Modulnamen mit global neu. make_counter gibt eine Closure zurück, die ihren eigenen Zähler mit nonlocal führt, sodass sich zwei Zähler keine Zahl teilen. Der letzte log-Aufruf liest len, das Python im eingebauten Scope findet.
main.py
from collections.abc import Callable
# Three ways a function reaches a name outside itself.
DEBUG = False
calls = 0
def enable_debug() -> None:
global DEBUG
DEBUG = True
def log(message: str) -> None:
global calls
calls += 1
if DEBUG:
print(f"[{calls}] {message}")
def make_counter(start: int) -> Callable[[], int]:
count = start
def step() -> int:
nonlocal count
count += 1
return count
return step
log("not shown: debug is off")
enable_debug()
log("debug is on")
tickets = make_counter(100)
orders = make_counter(0)
print(tickets(), tickets(), orders())
log(f"len is still the built-in: {len('abc')}")
print("calls:", calls)
Ausführen mit
python main.pyAusgabe
[2] debug is on
101 102 1
[3] len is still the built-in: 3
calls: 3- Der erste log-Aufruf wird gezählt, gibt aber nichts aus; die erste sichtbare Zeile ist daher [2].
- log liest DEBUG ohne Deklaration: Zum Lesen eines globalen Namens braucht es kein global.
- tickets und orders haben je ihr eigenes count, denn jeder Aufruf von make_counter legt ein neues an.
- Callable[[], int] ist der Typ einer Funktion ohne Argumente, die ein int zurückgibt.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 2
Ein Seitenaufruf-Zähler
record(page) soll views auf Modulebene um eins erhöhen, die Seite an history anhängen und den neuen Stand von views zurückgeben. reset() soll views auf 0 setzen und history leeren. Beide Funktionen sind kaputt: record löst UnboundLocalError aus, und reset lässt views unverändert. Reparieren Sie sie mit der richtigen Deklaration und lassen Sie history.append und history.clear, wie sie sind.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Beide Funktionen weisen views etwas zu, also behandelt Python views in beiden als lokal.
Hinweis 2
Schreiben Sie global views als erste Zeile in record und in reset.
Hinweis 3
history braucht keine Deklaration: append und clear ändern die Liste, ohne den Namen neu zu binden.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
views = 0
history: list[str] = []
def record(page: str) -> int:
"""Count one view of page and return the total so far."""
global views
views += 1
history.append(page)
return views
def reset() -> None:
"""Set the count back to 0 and forget the history."""
global views
views = 0
history.clear()
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
views = 0
history: list[str] = []
def record(page: str) -> int:
"""Count one view of page and return the total so far."""
views += 1
history.append(page)
return views
def reset() -> None:
"""Set the count back to 0 and forget the history."""
views = 0
history.clear()
test_main.py
import main
from main import record, reset
def test_record_counts():
"""record liefert nach einem reset erst 1, dann 2"""
reset()
got = record("/home"), record("/about")
assert got == (1, 2), f"record lieferte {got!r}, erwartet: (1, 2)"
def test_module_count():
"""Auch views auf Modulebene ändert sich"""
reset()
record("/home")
assert main.views == 1, f"main.views ist nach einem record {main.views!r}, erwartet: 1"
def test_history():
"""record merkt sich die Seiten in ihrer Reihenfolge"""
reset()
record("/a")
record("/b")
assert main.history == ["/a", "/b"], f"main.history ist {main.history!r}, erwartet: ['/a', '/b']"
def test_reset():
"""reset setzt views auf 0 und leert history"""
reset()
record("/a")
reset()
assert main.views == 0, f"main.views ist nach reset {main.views!r}, erwartet: 0"
assert main.history == [], f"main.history ist nach reset {main.history!r}, erwartet: []"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 2
Ein Ratenbegrenzer
make_limiter(limit) gibt eine Funktion allow() zurück. allow() liefert True für die ersten limit Aufrufe und False für jeden weiteren. Jeder Begrenzer zählt für sich: Zwei Begrenzer dürfen sich keinen Zähler teilen. Halten Sie den Zähler in make_limiter und ändern Sie ihn in allow mit nonlocal. Keine globalen Variablen.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
allow muss used ändern, und used gehört zu make_limiter, der umschließenden Funktion.
Hinweis 2
Beginnen Sie allow mit nonlocal used. Vergleichen Sie dann used mit limit.
Hinweis 3
if used >= limit: return False. Sonst erhöhen Sie used um eins und geben True zurück.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from collections.abc import Callable
def make_limiter(limit: int) -> Callable[[], bool]:
used = 0
def allow() -> bool:
nonlocal used
if used >= limit:
return False
used += 1
return True
return allow
Auf dem eigenen Computer ausführen
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
from collections.abc import Callable
def make_limiter(limit: int) -> Callable[[], bool]:
used = 0
def allow() -> bool:
# Return True for the first `limit` calls, False after that.
return True
return allow
test_main.py
from main import make_limiter
def test_first_calls_allowed():
"""Die ersten drei Aufrufe mit Limit 3 sind erlaubt"""
allow = make_limiter(3)
got = [allow() for _ in range(3)]
assert got == [True, True, True], f"die ersten drei Aufrufe lieferten {got!r}"
def test_then_refused():
"""Aufrufe nach dem Limit werden abgelehnt"""
allow = make_limiter(2)
got = [allow() for _ in range(4)]
assert got == [True, True, False, False], f"vier Aufrufe mit Limit 2 lieferten {got!r}"
def test_separate_counts():
"""Zwei Begrenzer zählen getrennt"""
a = make_limiter(1)
b = make_limiter(1)
got = a(), a(), b()
assert got == (True, False, True), f"a(), a(), b() lieferten {got!r}, erwartet: (True, False, True)"
def test_zero_limit():
"""Limit 0 lehnt schon den ersten Aufruf ab"""
allow = make_limiter(0)
got = allow()
assert got is False, f"mit Limit 0 lieferte der erste Aufruf {got!r}"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHäufige Fehler
Eine globale Variable ändern, ohne sie zu deklarieren
count = 0
def increment():
count += 1
increment()
Was Python ausgibt
UnboundLocalError: cannot access local variable 'count' where it is not associated with a valueWarum, und die Lösung
count += 1 ist eine Zuweisung, also ist count in increment lokal, und das lokale count hat keinen Wert, zu dem man addieren könnte. Meinen Sie wirklich das count auf Modulebene, schreiben Sie global count als erste Zeile der Funktion. Oft besser: den Wert übergeben und den neuen zurückgeben, count = increment(count).
nonlocal für einen Modulnamen
total = 0
def add(n):
nonlocal total
total += n
Was Python ausgibt
SyntaxError: no binding for nonlocal 'total' foundWarum, und die Lösung
nonlocal sucht nur in umschließenden Funktionen, und add steht in keiner anderen Funktion: total ist global. Python weist die Datei zurück, bevor irgendetwas läuft. Nehmen Sie global total für einen Modulnamen und nonlocal für Closures.
Einen eingebauten Namen mit einem eigenen verdecken
list = [3, 1, 2]
letters = list("abc")
Was Python ausgibt
TypeError: 'list' object is not callableWarum, und die Lösung
Der globale Name list zeigt jetzt auf Ihre Liste, und der globale Scope wird vor den eingebauten Namen durchsucht, also versucht list("abc"), eine Liste aufzurufen. Wählen Sie einen anderen Namen, etwa numbers. Dasselbe passiert mit str, dict, sum, max und id. In einer interaktiven Sitzung entfernt del list Ihren globalen Namen, und das eingebaute list ist wieder sichtbar.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.