Python & AI Tutorials Logo
Python Programmierung

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.

python
# 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: 0

In 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.

python
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 defined

Die 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:

python
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.0

Beide 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:

python
# 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:

python
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 UnboundLocalError

Dieser 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:

python
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 UnboundLocalError

Die 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:

python
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)  # NameError

Der 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:

python
# 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.80

In diesem Beispiel:

  • tax_rate und free_shipping_threshold sind globale Konfigurationswerte
  • subtotal, tax, shipping und total sind lokal für jeden Aufruf von calculate_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:

  1. Local (L): Der Scope der aktuellen Funktion
  2. Enclosing (E): Der Scope umschließender Funktionen (Funktionen, die die aktuelle Funktion enthalten)
  3. Global (G): Der Scope auf Modulebene
  4. Built-in (B): Pythons eingebaute Namen wie print, len, int usw.

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:

python
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.0

Wenn 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:

python
# 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.0

Wenn 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:

python
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:

python
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:

  1. Lokaler Scope von inner_function() – nicht gefunden
  2. 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:

python
# 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: 18

Wenn Python innerhalb von inner() z + y + multiplier auswertet:

  1. L (Local): Findet z = 3
  2. E (Enclosing): Findet y = 5 in outer()
  3. G (Global): Findet multiplier = 10
  4. 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):

python
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
global

Jede print()-Anweisung sieht ein anderes value, weil Python beim ersten Treffer stoppt:

  • Innerhalb von inner(): findet value lokal → druckt "local"
  • Innerhalb von outer(), aber außerhalb von inner(): findet value im Scope von outer() → druckt "enclosing"
  • Auf Modulebene: findet value global → druckt "global"

Visualisierung der LEGB-Suchreihenfolge

Ja

Nein

Ja

Nein

Ja

Nein

Ja

Nein

Namensreferenz

Im lokalen Scope gefunden?

Lokalen Wert verwenden

Im Enclosing-Scope gefunden?

Enclosing-Wert verwenden

Im globalen Scope gefunden?

Globalen Wert verwenden

Im Built-in-Scope gefunden?

Built-in-Wert verwenden

NameError

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:

  1. Variablenwerte vorherzusagen: Sie wissen genau, welche Variable Python verwenden wird
  2. Namenskonflikte zu vermeiden: Sie verstehen, wann Namen sich gegenseitig verdecken
  3. Bessere Funktionen zu entwerfen: Sie können entscheiden, welcher Scope für jede Variable passend ist
  4. 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:

python
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 UnboundLocalError

Das 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.“

python
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: 2

Die 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:

python
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: 2

Sowohl 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:

python
# 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: 2

Dieses 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:

python
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:

python
def add_to_total(current_total, value):
    return current_total + value
 
total = 0
total = add_to_total(total, 10)  # Klar: total wird aktualisiert

Die 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:

python
# 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 == 20

Problem 3: Funktionen sind nicht wiederverwendbar

Funktionen, die von bestimmten globalen Variablen abhängen, können nicht leicht in anderen Programmen wiederverwendet werden:

python
# 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:

python
# 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: 900

Rückgabewerte verwenden:

python
# 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: 900

Die zweite Version ist flexibler, besser testbar und leichter wiederverwendbar.

Wann global tatsächlich angemessen ist

Es gibt legitime Anwendungsfälle für global:

  1. Konfiguration, die wirklich global sein muss:
python
# Anwendungsweite Einstellungen
debug_mode = False
log_level = "INFO"
 
def enable_debug():
    global debug_mode, log_level
    debug_mode = True
    log_level = "DEBUG"
  1. Zähler für Debugging oder Statistiken:
python
# Funktionsaufrufe für Debugging tracken
_function_call_count = 0
 
def tracked_function():
    global _function_call_count
    _function_call_count += 1
    # ... rest of function

Wichtigste Erkenntnisse zu global

  • Verwenden Sie global nur, wenn Sie wirklich Zustand auf Modulebene verändern müssen
  • Bevorzugen Sie stattdessen Rückgabewerte und Parameter
  • Wenn Sie global verwenden, dokumentieren Sie, warum es notwendig ist
  • Überlegen Sie, ob Ihr Design verbessert werden kann, um global zu 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:

python
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 UnboundLocalError

Python 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.“

python
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:

python
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: 2

Jeder 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:

python
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()
  • global bezieht sich immer auf den Scope auf Modulebene
  • nonlocal bezieht 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:

  1. Globalen Scope (verwenden Sie stattdessen global):
python
x = "global"
 
def func():
    nonlocal x  # SyntaxError: no binding for nonlocal 'x' found
    x = "modified"
  1. Variablen, die in keinem umschließenden Scope existieren:
python
def outer():
    def inner():
        nonlocal count  # SyntaxError: no binding for nonlocal 'count' found

Wichtigste Erkenntnisse zu nonlocal

  • Verwenden Sie nonlocal, um Variablen aus umschließenden Funktionsscopes zu verändern
  • nonlocal durchsucht umschließende Funktionsscopes, nicht den globalen Scope
  • Das Lesen umschließender Variablen erfordert kein nonlocal – nur das Verändern
  • nonlocal ermö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:

python
x = 42
print(x)  # Output: 42
 
del x
 
# print(x)  # NameError: name 'x' is not defined

Nach 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:

python
# 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 defined

Die 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):

python
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.

python
# 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'}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai