21. Variablen-Scope und Namensauflösung
Wenn Sie in Python eine Variable erstellen, wo „lebt“ sie dann? Kann eine Funktion (function) Variablen sehen, die außerhalb von ihr erstellt wurden? Kann Code außerhalb einer Funktion Variablen erreichen, die innerhalb von ihr erstellt wurden? Diese Fragen drehen sich um den Scope (scope) – den Bereich Ihres Programms, in dem ein Name sichtbar ist und verwendet werden kann.
Den Scope zu verstehen, ist entscheidend, um Funktionen zu schreiben, die korrekt und vorhersehbar funktionieren. Ohne dieses Wissen könnten Sie versehentlich Bugs erzeugen, bei denen Variablen nicht die Werte haben, die Sie erwarten, oder bei denen Änderungen an Variablen nicht wie beabsichtigt bestehen bleiben.
In diesem Kapitel erkunden wir, wie Python bestimmt, auf welche Variable sich ein Name bezieht, wie Sie steuern, wo Variablen zugänglich sind, und was passiert, wenn Sie einen Namen löschen. Am Ende werden Sie die Regeln verstehen, die die Sichtbarkeit von Variablen in Python-Programmen bestimmen.
21.1) Lokale und globale Variablen
Jede Variable in Python existiert innerhalb eines bestimmten Scope (scope) – eines Codebereichs, in dem dieser Variablenname definiert und zugänglich ist. Die zwei grundlegendsten Scopes sind lokal und global.
Globalen Scope verstehen
Variablen, die auf der obersten Ebene Ihres Programms erstellt werden – außerhalb jeder Funktion – existieren im globalen Scope. Diese heißen globale Variablen, und sie sind überall in Ihrem Modul zugänglich, nachdem sie definiert wurden.
# Globale Variable – auf Modulebene definiert
total_users = 0
def show_user_count():
# Diese Funktion kann die globale Variable LESEN
print(f"Total users: {total_users}")
show_user_count() # Output: Total users: 0
print(total_users) # Output: 0In diesem Beispiel ist total_users eine globale Variable. Sowohl die Funktion show_user_count() als auch der Code auf Modulebene können darauf zugreifen. Stellen Sie sich globale Variablen so vor, dass sie in Ihrer gesamten Programmdatei sichtbar sind.
Lokalen Scope verstehen
Variablen, die innerhalb einer Funktion erstellt werden, existieren im lokalen Scope dieser Funktion. Diese heißen lokale Variablen, und sie sind nur innerhalb der Funktion zugänglich, in der sie definiert sind. Sobald die Funktion mit der Ausführung fertig ist, verschwinden lokale Variablen.
def calculate_discount(price):
# discount_rate ist für diese Funktion LOKAL
discount_rate = 0.15
discount_amount = price * discount_rate
return discount_amount
result = calculate_discount(100)
print(result) # Output: 15.0
# Das würde einen Fehler verursachen – discount_rate existiert hier nicht
# print(discount_rate) # NameError: name 'discount_rate' is not definedDie Variablen discount_rate und discount_amount existieren nur, während calculate_discount() läuft. Nachdem die Funktion zurückkehrt, existieren diese Namen nicht mehr. Das ist tatsächlich eine gute Sache – es verhindert, dass Funktionen Ihr Programm mit temporären Variablen zumüllen.
Warum lokaler Scope wichtig ist
Lokaler Scope bietet Kapselung (encapsulation) – jede Funktion hat ihren eigenen privaten Arbeitsbereich. Das bedeutet, Sie können in verschiedenen Funktionen dieselben Variablennamen verwenden, ohne Konflikte:
def calculate_tax(amount):
rate = 0.08 # Lokale Variable
return amount * rate
def calculate_shipping(weight):
rate = 5.00 # Andere lokale Variable mit demselben Namen
return weight * rate
tax = calculate_tax(100)
shipping = calculate_shipping(3)
print(f"Tax: ${tax}") # Output: Tax: $8.0
print(f"Shipping: ${shipping}") # Output: Shipping: $15.0Beide Funktionen verwenden eine Variable namens rate, aber es sind völlig getrennte Variablen in unterschiedlichen lokalen Scopes. Änderungen an rate in einer Funktion beeinflussen rate in der anderen Funktion nicht. Diese Isolation macht Funktionen zuverlässiger und leichter zu verstehen.
Globale Variablen aus Funktionen lesen
Funktionen können globale Variablen ohne besondere Syntax lesen:
# Globale Konfiguration
max_login_attempts = 3
def check_login(password):
# Globale Variable lesen
if password == "secret123":
return "Login successful"
else:
return f"Invalid password. You have {max_login_attempts} attempts."
result = check_login("wrong")
print(result) # Output: Invalid password. You have 3 attempts.Die Funktion check_login() kann max_login_attempts lesen, weil es eine globale Variable ist. Es gibt jedoch eine wichtige Einschränkung, die wir verstehen müssen.
Die Regel: Zuweisung erzeugt lokale Variablen
Hier wird Scope knifflig. Wenn Sie innerhalb einer Funktion einem Variablennamen einen Wert zuweisen, erstellt Python eine neue lokale Variable mit diesem Namen, selbst wenn eine globale Variable mit demselben Namen existiert:
counter = 0 # Globale Variable
def increment_counter():
# WARNUNG: Das erstellt eine NEUE lokale Variable namens counter – nur zur Demonstration
# PROBLEM: Versuch, counter zu lesen, bevor lokal etwas zugewiesen wird
counter = counter + 1 # UnboundLocalError: local variable 'counter' referenced before assignment
print(counter)
# increment_counter() # Dieser Aufruf führt zu UnboundLocalErrorDieser Code schlägt fehl, weil Python die Zuweisung counter = counter + 1 sieht und entscheidet, dass counter eine lokale Variable sein muss. Aber wenn es dann versucht, counter + 1 auszuwerten, hat die lokale Variable counter noch keinen Wert – wir versuchen, sie zu verwenden, bevor wir ihr etwas zugewiesen haben.
Das ist eine häufige Quelle von Verwirrung. Die Regel lautet: Wenn eine Funktion irgendwo in ihrem Rumpf einem Variablennamen einen Wert zuweist, wird dieser Name in der gesamten Funktion als lokal behandelt, sogar vor der Zuweisung.
Sehen wir uns das klarer an:
message = "Hello" # Globale Variable
def show_message():
print(message) # Das funktioniert – es wird nur die globale gelesen
def change_message():
# WARNUNG: Das demonstriert einen häufigen Fehler – nur zur Demonstration
# PROBLEM: Python sieht unten eine Zuweisung, daher wird message überall als lokal behandelt
print(message) # UnboundLocalError!
message = "Goodbye" # Das macht message für die GANZE Funktion lokal
show_message() # Output: Hello
# change_message() # Dieser Aufruf führt zu UnboundLocalErrorDie Funktion show_message() funktioniert einwandfrei, weil sie message nur liest. Aber change_message() schlägt fehl, weil die Zuweisung in der zweiten Zeile Python dazu bringt, message in der gesamten Funktion als lokal zu behandeln – einschließlich der print()-Anweisung, die vor der Zuweisung kommt.
Parameter sind lokale Variablen
Funktionsparameter sind lokale Variablen, die ihre Anfangswerte aus den Argumenten erhalten, die beim Aufruf der Funktion übergeben werden:
def greet(name): # 'name' ist eine lokale Variable
greeting = f"Hello, {name}!" # 'greeting' ist ebenfalls lokal
return greeting
message = greet("Alice")
print(message) # Output: Hello, Alice!
# Weder 'name' noch 'greeting' existieren hier
# print(name) # NameErrorDer Parameter name existiert nur innerhalb der Funktion greet(). Er wird erstellt, wenn die Funktion aufgerufen wird, und verschwindet, wenn die Funktion zurückkehrt.
Praktisches Beispiel: Berechnung eines Warenkorbs
Sehen wir uns an, wie lokaler und globaler Scope in einem realistischen Szenario zusammenarbeiten:
# Globale Konfiguration
tax_rate = 0.08
free_shipping_threshold = 50
def calculate_total(subtotal):
# Lokale Variablen für diese Berechnung
tax = subtotal * tax_rate # Globales tax_rate lesen
# Versandkosten bestimmen
if subtotal >= free_shipping_threshold: # Globalen Schwellenwert lesen
shipping = 0
else:
shipping = 5.99
total = subtotal + tax + shipping
return total
# Für unterschiedliche Warenkorbwerte berechnen
cart1 = calculate_total(30)
cart2 = calculate_total(60)
print(f"Cart 1 total: ${cart1:.2f}") # Output: Cart 1 total: $38.39
print(f"Cart 2 total: ${cart2:.2f}") # Output: Cart 2 total: $64.80In diesem Beispiel:
tax_rateundfree_shipping_thresholdsind globale Konfigurationswertesubtotal,tax,shippingundtotalsind lokal für jeden Aufruf voncalculate_total()- Jeder Funktionsaufruf erhält seinen eigenen, separaten Satz lokaler Variablen
- Die Funktion kann die globale Konfiguration lesen, sie aber nicht verändern
Diese Trennung der Zuständigkeiten macht den Code klar: Globale Variablen halten Konfiguration, die überall gilt, während lokale Variablen temporäre Berechnungsergebnisse halten, die spezifisch für jeden Funktionsaufruf sind.
21.2) Die LEGB-Regel für die Namensauflösung
Wenn Python auf einen Variablennamen trifft, wie weiß es dann, auf welche Variable Sie sich beziehen? Python folgt einer bestimmten Suchreihenfolge namens LEGB-Regel (LEGB rule). LEGB steht für Local, Enclosing, Global, Built-in – die vier Scopes, die Python in dieser Reihenfolge durchsucht.
Die vier Scopes in LEGB
Verstehen wir jeden Scope in der LEGB-Hierarchie:
- Local (L): Der Scope der aktuellen Funktion
- Enclosing (E): Der Scope umschließender Funktionen (Funktionen, die die aktuelle Funktion enthalten)
- Global (G): Der Scope auf Modulebene
- Built-in (B): Pythons eingebaute Namen wie
print,len,intusw.
Wenn Sie einen Variablennamen verwenden, durchsucht Python diese Scopes der Reihe nach: L → E → G → B. Es verwendet den ersten Treffer, den es findet, und beendet dann die Suche.
Lokaler Scope: Der erste Ort, an dem Python sucht
Python prüft immer zuerst den lokalen Scope:
def calculate_price():
price = 100 # Lokale Variable
tax = 0.08 # Lokale Variable
total = price * (1 + tax)
return total
result = calculate_price()
print(result) # Output: 108.0Wenn Python innerhalb von calculate_price() price, tax und total sieht, findet es sie im lokalen Scope und verwendet diese Werte. Die Suche endet im lokalen Scope – Python muss nicht weiter nach außen schauen.
Globaler Scope: Wenn es lokal nicht vorhanden ist
Wenn ein Name lokal nicht gefunden wird, prüft Python den globalen Scope:
# Globale Variablen
default_tax_rate = 0.08
default_currency = "USD"
def calculate_price(amount):
# 'amount' ist lokal, wird sofort gefunden
# 'default_tax_rate' ist nicht lokal, wird im globalen Scope gefunden
total = amount * (1 + default_tax_rate)
return total
result = calculate_price(100)
print(result) # Output: 108.0Wenn Python innerhalb der Funktion auf default_tax_rate stößt, findet es diesen Namen nicht lokal, also durchsucht es den globalen Scope und findet ihn dort.
Built-in-Scope: Pythons vordefinierte Namen
Wenn ein Name weder im lokalen noch im globalen Scope gefunden wird, prüft Python den Built-in-Scope – die Namen, die Python automatisch bereitstellt:
def process_data(numbers):
# 'numbers' ist lokal
# 'len' ist weder lokal noch global – es ist built-in
count = len(numbers)
# 'max' ist ebenfalls built-in
maximum = max(numbers)
return count, maximum
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result) # Output: (5, 30)Die Namen len und max sind in Ihrem Code nicht definiert – es sind eingebaute Funktionen, die Python bereitstellt. Wenn Python diese Namen weder lokal noch global findet, prüft es den Built-in-Scope und findet sie dort.
Enclosing-Scope: Verschachtelte Funktionen
Der Enclosing-Scope kommt ins Spiel, wenn Sie verschachtelte Funktionen haben – Funktionen, die innerhalb anderer Funktionen definiert werden. Hier wird das „E“ in LEGB wichtig:
def outer_function():
outer_var = "I'm from outer" # Im Enclosing-Scope für inner_function
def inner_function():
inner_var = "I'm from inner" # Lokal für inner_function
# inner_function kann sowohl inner_var (lokal) als auch outer_var (enclosing) sehen
print(inner_var) # Output: I'm from inner
print(outer_var) # Output: I'm from outer
inner_function()
outer_function()Für inner_function() ist der Scope von outer_function() ein Enclosing-Scope. Wenn inner_function() outer_var referenziert, sucht Python:
- Lokaler Scope von
inner_function()– nicht gefunden - Enclosing-Scope von
outer_function()– gefunden! Verwendet diesen Wert
LEGB in Aktion: Einfaches Beispiel
Sehen wir uns alle vier Scopes zusammen in einem klaren, geradlinigen Beispiel an:
# Built-in: len (Python stellt das bereit)
# Global: multiplier
multiplier = 10
def outer(x):
# Enclosing-Scope für inner
y = 5
def inner(z):
# Lokaler Scope
# z ist lokal (L)
# y ist aus dem Enclosing-Scope (E)
# multiplier ist aus dem globalen Scope (G)
# len ist aus dem Built-in-Scope (B)
result = len([z, y, multiplier]) # Nutzt alle vier Scopes!
return z + y + multiplier
return inner(3)
answer = outer(100)
print(answer) # Output: 18Wenn Python innerhalb von inner() z + y + multiplier auswertet:
- L (Local): Findet
z = 3 - E (Enclosing): Findet
y = 5inouter() - G (Global): Findet
multiplier = 10 - B (Built-in): Findet die Funktion
len
Dieses Beispiel demonstriert klar, wie Python durch alle vier Scopes sucht, um Namen aufzulösen.
Shadowing: Wenn innere Scopes äußere Namen verdecken
Wenn derselbe Name in mehreren Scopes existiert, „gewinnt“ der innerste Scope – das nennt man Shadowing (shadowing):
value = "global"
def outer():
value = "enclosing"
def inner():
value = "local"
print(value) # Welcher Wert?
inner()
print(value) # Welcher Wert?
outer()
print(value) # Welcher Wert?Ausgabe:
local
enclosing
globalJede print()-Anweisung sieht ein anderes value, weil Python beim ersten Treffer stoppt:
- Innerhalb von
inner(): findetvaluelokal → druckt "local" - Innerhalb von
outer(), aber außerhalb voninner(): findetvalueim Scope vonouter()→ druckt "enclosing" - Auf Modulebene: findet
valueglobal → druckt "global"
Visualisierung der LEGB-Suchreihenfolge
Dieses Diagramm zeigt Pythons Suchprozess. Er beginnt beim innersten Scope und arbeitet sich nach außen. Wenn der Name in keinem Scope gefunden wird, löst Python einen NameError aus.
Warum LEGB beim Schreiben von Funktionen wichtig ist
Das Verständnis von LEGB hilft Ihnen:
- Variablenwerte vorherzusagen: Sie wissen genau, welche Variable Python verwenden wird
- Namenskonflikte zu vermeiden: Sie verstehen, wann Namen sich gegenseitig verdecken
- Bessere Funktionen zu entwerfen: Sie können entscheiden, welcher Scope für jede Variable passend ist
- Scope-Probleme zu debuggen: Wenn Variablen nicht die erwarteten Werte haben, können Sie LEGB Schritt für Schritt nachverfolgen
Die LEGB-Regel ist grundlegend dafür, wie Python Namen auflöst. Jedes Mal, wenn Sie eine Variable verwenden, folgt Python im Hintergrund dieser Regel.
21.3) Das Keyword global sorgfältig verwenden
Wir haben gesehen, dass Funktionen globale Variablen lesen können, aber was ist, wenn Sie eine globale Variable innerhalb einer Funktion verändern müssen? Dafür gibt es das Keyword global – aber Sie sollten es sparsam und vorsichtig einsetzen.
Das Problem: Zuweisung erzeugt lokale Variablen
Wie wir vorhin gelernt haben, erzeugt eine Zuweisung an eine Variable innerhalb einer Funktion eine lokale Variable:
counter = 0 # Globale Variable
def increment():
# WARNUNG: Das erstellt eine NEUE lokale Variable namens counter – nur zur Demonstration
# PROBLEM: Versuch, counter zu lesen, bevor lokal etwas zugewiesen wird
counter = counter + 1 # UnboundLocalError!
# increment() # Dieser Aufruf führt zu UnboundLocalErrorDas schlägt fehl, weil Python die Zuweisung sieht und counter in der gesamten Funktion als lokal behandelt. Aber wir versuchen, counter zu lesen, bevor wir ihm lokal etwas zugewiesen haben.
Das ist einer der häufigsten Fehler beim Arbeiten mit globalen Variablen. Die Fehlermeldung UnboundLocalError: local variable 'counter' referenced before assignment sagt Ihnen genau, was passiert ist: Python hat entschieden, dass counter lokal ist (wegen der Zuweisung), aber Sie haben versucht, es zu verwenden, bevor Sie ihm einen Wert gegeben haben.
Die Lösung: Variablen als global deklarieren
Das Keyword global sagt Python: „Erstelle keine neue lokale Variable mit diesem Namen. Verwende stattdessen die globale Variable.“
counter = 0 # Globale Variable
def increment():
global counter # Python sagen, dass es den globalen counter verwenden soll
counter = counter + 1 # Jetzt wird die globale Variable geändert
print(f"Before: {counter}") # Output: Before: 0
increment()
print(f"After: {counter}") # Output: After: 1
increment()
print(f"After again: {counter}") # Output: After again: 2Die Deklaration global counter muss kommen, bevor Sie die Variable verwenden. Sie sagt Python, dass alle Zuweisungen an counter in dieser Funktion die globale Variable verändern sollen und keine lokale erzeugen.
Mehrere globale Variablen
Sie können mehrere Variablen in einer Anweisung als global deklarieren:
total_sales = 0
total_customers = 0
def record_sale(amount):
global total_sales, total_customers
total_sales += amount
total_customers += 1
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $0, Customers: 0
record_sale(25.50)
record_sale(30.00)
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $55.5, Customers: 2Sowohl total_sales als auch total_customers werden als global deklariert, daher kann die Funktion beide verändern.
Wann man global verwendet: Shared State
Das Keyword global ist sinnvoll, wenn Sie gemeinsamen Zustand (shared state) pflegen müssen – Daten, auf die mehrere Funktionen zugreifen und die sie verändern müssen:
# Spielzustand
player_score = 0
player_lives = 3
game_over = False
def award_points(points):
global player_score
player_score += points
print(f"Score: {player_score}")
def lose_life():
global player_lives, game_over
player_lives -= 1
print(f"Lives remaining: {player_lives}")
if player_lives <= 0:
game_over = True
print("Game Over!")
def check_game_status():
# Nur Globals lesen – kein global-Keyword nötig
if game_over:
return "Game Over"
else:
return f"Playing - Score: {player_score}, Lives: {player_lives}"
# Das Spiel spielen
award_points(100) # Output: Score: 100
award_points(50) # Output: Score: 150
lose_life() # Output: Lives remaining: 2
print(check_game_status()) # Output: Playing - Score: 150, Lives: 2Dieses Beispiel zeigt eine passende Verwendung von global: Mehrere Funktionen müssen gemeinsamen Spielzustand verändern. Beachten Sie jedoch, dass check_game_status() kein global benötigt, weil es die Variablen nur liest.
Warum global vorsichtig verwendet werden sollte
Auch wenn global manchmal notwendig ist, kann übermäßiger Einsatz Code schwerer verständlich und wartbarer machen. Hier ist der Grund:
Problem 1: Versteckte Abhängigkeiten
Wenn Funktionen globale Variablen verändern, ist aus dem Funktionsaufruf nicht ersichtlich, was sich ändert:
total = 0
def add_to_total(value):
global total
total += value
# Was macht diese Funktion? Das können Sie nicht erkennen, ohne ihren Code zu lesen
add_to_total(10)Vergleichen Sie das mit einer Funktion, die einen Wert zurückgibt:
def add_to_total(current_total, value):
return current_total + value
total = 0
total = add_to_total(total, 10) # Klar: total wird aktualisiertDie zweite Version macht explizit, dass total verändert wird.
Problem 2: Testen wird schwieriger
Funktionen, die globalen Zustand verändern, sind schwerer zu testen, weil Sie globale Variablen vorbereiten und zurücksetzen müssen:
# Schwer zu testen – hängt vom globalen Zustand ab
score = 0
def add_score(points):
global score
score += points
# Jeder Test muss score zurücksetzen
# Test 1
score = 0
add_score(10)
assert score == 10
# Test 2 – score muss wieder zurückgesetzt werden
score = 0
add_score(20)
assert score == 20Problem 3: Funktionen sind nicht wiederverwendbar
Funktionen, die von bestimmten globalen Variablen abhängen, können nicht leicht in anderen Programmen wiederverwendet werden:
# Diese Funktion funktioniert nur, wenn es eine globale Variable namens 'inventory' gibt
inventory = []
def add_item(item):
global inventory
inventory.append(item)Bessere Alternativen zu global
In vielen Fällen können Sie global vermeiden, indem Sie Rückgabewerte und Parameter verwenden:
Statt globalen Zustand zu verändern:
# global verwenden (weniger ideal)
balance = 1000
def withdraw(amount):
global balance
if amount <= balance:
balance -= amount
return True
return False
withdraw(100)
print(balance) # Output: 900Rückgabewerte verwenden:
# Rückgabewerte verwenden (besser)
def withdraw(balance, amount):
if amount <= balance:
return balance - amount, True
return balance, False
balance = 1000
balance, success = withdraw(balance, 100)
print(balance) # Output: 900Die zweite Version ist flexibler, besser testbar und leichter wiederverwendbar.
Wann global tatsächlich angemessen ist
Es gibt legitime Anwendungsfälle für global:
- Konfiguration, die wirklich global sein muss:
# Anwendungsweite Einstellungen
debug_mode = False
log_level = "INFO"
def enable_debug():
global debug_mode, log_level
debug_mode = True
log_level = "DEBUG"- Zähler für Debugging oder Statistiken:
# Funktionsaufrufe für Debugging tracken
_function_call_count = 0
def tracked_function():
global _function_call_count
_function_call_count += 1
# ... rest of functionWichtigste Erkenntnisse zu global
- Verwenden Sie
globalnur, wenn Sie wirklich Zustand auf Modulebene verändern müssen - Bevorzugen Sie stattdessen Rückgabewerte und Parameter
- Wenn Sie
globalverwenden, dokumentieren Sie, warum es notwendig ist - Überlegen Sie, ob Ihr Design verbessert werden kann, um
globalzu vermeiden - Denken Sie daran: Das Lesen globaler Variablen erfordert kein
global-Keyword – nur das Verändern
21.4) nonlocal verwenden, um Variablen in umschließenden Funktionen zu ändern
Wenn Sie verschachtelte Funktionen haben, müssen Sie möglicherweise eine Variable aus dem Scope einer umschließenden Funktion verändern. Das Keyword nonlocal dient genau diesem Zweck – es ist wie global, aber für umschließende Funktionsscopes statt für den globalen Scope.
Das Problem: Umschließende Variablen verändern
So wie Zuweisung standardmäßig lokale Variablen erzeugt, tritt dasselbe Problem auch bei Enclosing-Scopes auf:
def outer():
count = 0 # Variable im Scope von outer
def inner():
# WARNUNG: Das erstellt eine NEUE lokale Variable namens count – nur zur Demonstration
# PROBLEM: Versuch, count zu lesen, bevor lokal etwas zugewiesen wird
count = count + 1 # UnboundLocalError!
print(count)
inner()
# outer() # Dieser Aufruf führt zu UnboundLocalErrorPython sieht die Zuweisung an count in inner() und behandelt es als lokale Variable. Aber wir versuchen, es zu lesen, bevor wir ihm lokal etwas zugewiesen haben, was einen Fehler verursacht.
Die Lösung: Das Keyword nonlocal
Das Keyword nonlocal sagt Python: „Diese Variable ist nicht lokal – suche sie im Scope der umschließenden Funktion und verwende diese.“
def outer():
count = 0 # Variable im Scope von outer
def inner():
nonlocal count # Das count aus dem Scope von outer verwenden
count = count + 1
print(f"Count in inner: {count}")
print(f"Count before: {count}") # Output: Count before: 0
inner() # Output: Count in inner: 1
print(f"Count after: {count}") # Output: Count after: 1
outer()Jetzt kann inner() die Variable count aus dem Scope von outer() verändern. Die Änderung bleibt nach dem Return von inner() erhalten, weil wir die tatsächliche Variable im umschließenden Scope verändern.
Warum nonlocal nützlich ist: Funktionen, die sich Zustand merken
Das Keyword nonlocal ermöglicht ein mächtiges Muster, bei dem innere Funktionen Zustand aus ihrem umschließenden Scope behalten und verändern können. Wir werden Closures und Factory-Funktionen in Kapitel 23 detailliert lernen, aber fürs Erste sollten Sie verstehen, dass nonlocal inneren Funktionen erlaubt, Variablen aus umschließenden Scopes zu verändern.
Hier ist ein einfaches Beispiel, das zeigt, wie nonlocal funktioniert:
def create_counter():
count = 0 # Diese Variable liegt im Enclosing-Scope für increment
def increment():
nonlocal count # Das count aus dem Enclosing-Scope verändern
count += 1
return count
return increment # Die innere Funktion zurückgeben
# Einen Zähler erstellen
counter1 = create_counter()
print(counter1()) # Output: 1
print(counter1()) # Output: 2
print(counter1()) # Output: 3
# Einen weiteren unabhängigen Zähler erstellen
counter2 = create_counter()
print(counter2()) # Output: 1
print(counter2()) # Output: 2Jeder Aufruf von create_counter() erstellt eine neue Variable count und eine neue Funktion increment(), die dieses spezifische count mit nonlocal verändern kann.
nonlocal vs global
Es ist wichtig, den Unterschied zu verstehen:
x = "global"
def outer():
x = "enclosing"
def use_global():
global x # Bezieht sich auf das globale x
print(f"use_global sees: {x}") # Output: use_global sees: global
def use_nonlocal():
nonlocal x # Bezieht sich auf outers x
print(f"use_nonlocal sees: {x}") # Output: use_nonlocal sees: enclosing
use_global()
use_nonlocal()
outer()globalbezieht sich immer auf den Scope auf Modulebenenonlocalbezieht sich auf den nächstgelegenen umschließenden Funktionsscope
Wann Sie nonlocal nicht verwenden können
Das Keyword nonlocal funktioniert nur mit umschließenden Funktionsscopes. Sie können es nicht verwenden für:
- Globalen Scope (verwenden Sie stattdessen
global):
x = "global"
def func():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = "modified"- Variablen, die in keinem umschließenden Scope existieren:
def outer():
def inner():
nonlocal count # SyntaxError: no binding for nonlocal 'count' foundWichtigste Erkenntnisse zu nonlocal
- Verwenden Sie
nonlocal, um Variablen aus umschließenden Funktionsscopes zu verändern nonlocaldurchsucht umschließende Funktionsscopes, nicht den globalen Scope- Das Lesen umschließender Variablen erfordert kein
nonlocal– nur das Verändern nonlocalermöglicht mächtige Muster, um Funktionen mit privatem Zustand zu erstellen- Wir lernen mehr über Closures und Factory-Funktionen in Kapitel 23
Das Keyword nonlocal ist besonders nützlich, um Funktionen zu erstellen, die privaten Zustand behalten, wie wir es bei den Beispielen mit Zähler, Konto und Event-Tracker gesehen haben.
21.5) Namen (nicht Objekte) mit del löschen und was das bedeutet
Manchmal müssen Sie eine Variable aus dem Namespace Ihres Programms entfernen – vielleicht um Speicher in lang laufenden Programmen freizugeben, temporäre Variablen aufzuräumen oder Einträge aus Collections zu entfernen. Pythons Anweisung del erledigt diese Aufgaben, aber es ist wichtig, genau zu verstehen, was sie tut und was nicht.
Die Anweisung del in Python wird oft missverstanden. Sie löscht keine Objekte – sie löscht Namen (Variablenbindungen). Diesen Unterschied zu verstehen ist entscheidend, um zu verstehen, wie Python Speicher und Referenzen verwaltet.
Was del tatsächlich macht
Die Anweisung del entfernt einen Namen aus dem aktuellen Scope:
x = 42
print(x) # Output: 42
del x
# print(x) # NameError: name 'x' is not definedNach del x existiert der Name x im aktuellen Scope nicht mehr. Wenn Sie versuchen, ihn zu verwenden, löst Python einen NameError aus, weil der Name nicht mehr definiert ist.
Namen löschen vs. Objekte löschen
Das ist die zentrale Erkenntnis: del entfernt den Namen, aber nicht unbedingt das Objekt, auf das der Name verweist:
# Eine Liste erstellen und zwei Namen, die darauf verweisen
original = [1, 2, 3]
reference = original # Beide Namen verweisen auf dieselbe Liste
print(original) # Output: [1, 2, 3]
print(reference) # Output: [1, 2, 3]
# Einen Namen löschen
del original
# Die Liste existiert weiterhin, weil 'reference' noch darauf verweist
print(reference) # Output: [1, 2, 3]
# print(original) # NameError: name 'original' is not definedDie Liste [1, 2, 3] existiert weiter, weil reference noch darauf verweist. Das Löschen von original entfernt nur diesen Namen – es löscht nicht das Listenobjekt selbst.
Wann Objekte tatsächlich gelöscht werden
Python löscht Objekte automatisch, wenn sie nicht mehr von irgendeinem Namen referenziert werden. Das nennt man Garbage Collection (garbage collection):
data = [1, 2, 3] # Liste wird erstellt, 'data' verweist darauf
del data # Der Name 'data' wird gelöscht
# Jetzt hat die Liste keine Referenzen mehr, also wird Python sie irgendwann löschen
# (Das passiert automatisch – Sie müssen nichts tun)Wenn wir data löschen, hat die Liste [1, 2, 3] keine verbleibenden Referenzen, daher wird Pythons Garbage Collector den Speicher irgendwann zurückgewinnen. Aber das passiert automatisch – Sie kontrollieren nicht, wann.
Elemente aus Collections löschen
Die Anweisung del kann auch Elemente aus Collections entfernen, aber das ist grundlegend anders als das Löschen von Namen. Wenn Sie del mit Indexierung oder Slicing einer Collection verwenden, verändern Sie die Collection selbst, statt einen Namen zu löschen.
Das ist ein wichtiger Unterschied: Wenn Sie del numbers[2] schreiben, rufen Sie eine spezielle Methode des Listenobjekts auf, um ein Element zu entfernen. Der Name numbers existiert weiterhin und verweist weiterhin auf dasselbe Listenobjekt – die Liste hat jetzt nur weniger Elemente.
# Listenelemente per Index löschen
numbers = [10, 20, 30, 40, 50]
del numbers[2] # Das Element an Index 2 entfernen
print(numbers) # Output: [10, 20, 40, 50]
# Listen-Slices löschen
numbers = [10, 20, 30, 40, 50]
del numbers[1:3] # Elemente von Index 1 bis 3 (exklusiv) entfernen
print(numbers) # Output: [10, 40, 50]
# Dictionary-Einträge löschen
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person) # Output: {'name': 'Alice', 'city': 'Boston'}