Scopes in Python (Gültigkeitsbereiche)      (C) 2017-2021 T.Birnthaler OSTC GmbH
======================================

Folgende LEGB-Regel gilt bezügliche der REIHENFOLGE der durchsuchten Scopes
(Gültigkeitsbereiche) bei einem "Name-Lookup" von oben nach unten. Beim ersten
Treffer ist die Suche zu Ende:

 +------------+-------------------------------------------+  ||
 | L)ocal     | Funktion (auch Lambda)                    |  ||
 | E)nclosing | Einschachtelnde Funktion (z.B. Dekorator) |  ||
 | G)lobal    | Hauptprogramm (Modul)                     |  ||
 | B)uilt-in  | Python-Interpreter (z.B. len, sum, abs)   |  ||
 +------------+-------------------------------------------+  \/

ACHTUNG: Es gibt KEINE Block-lokalen Namen in Python (KEINE Block-Scopes,
Blöcke werden durch gleichförmiges Einrücken nach einem ":" gebildet. Auch
Laufvariablen von for-Schleifen sind global (und enthalten den letzten
erreichten Wert):

  var = 0                   # var ist globale Variable
  for i in range(1,10+1):   # i ist globale Variable
      j = 10                # j ist globale Variable
      var += i              # var, i sind globale Variable
  print(var, i, j)          # var, i, j sind globale Variable

Folgendes Code-Stück zeigt dieses LEGB-Verhalten beim "Name-Lookup" (R =
Ausgabe-Reihenfolge)

 +------------------------+--------------------+---+-------------------------+
 | Code                   | Aktion             | R | Ausgabe                 |
 +------------------------+--------------------+---+-------------------------+
 | print(len)             |                    | 1 | <built-in function len> |
 | def f_aussen():        | Def. Funktion      |   |                         |
 |     len = "E)nclosing" | Def. lok. Var.     |   |                         |
 |     print(len)         |                    | 3 | E)nclosing              |
 |     def f_innen():     | Def. Funktion      |   |                         |
 |         len = "L)okal" | Def. lok. Var.     |   |                         |
 |         print(len)     |                    | 5 | L)ocal                  |
 |     print(len)         |                    | 4 | E)nclosing              |
 |     f_innen()          | Aufruf "f_innen"   |   |                         |
 |     print(len)         |                    | 6 | E)nclosing              |
 | print(len)             |                    | 2 | <built-in function len> |
 | f_aussen()             | Aufruf "f_aussen"  |   |                         |
 | print(len)             |                    | 7 | <built-in function len> |
 | len = "G)lobal"        | Def. glob. Var.    |   |                         |
 | print(len)             |                    | 8 | G)lobal                 |
 | del len                | Löschen glob. Var. |   |                         |
 | print(len)             |                    | 9 | <built-in function len> |
 +------------------------+--------------------+---+-------------------------+

Auf eine GLOBALE Variable kann in Funktionen LESEND zugegriffen werden,
falls keine gleichnamige LOKALE Variable durch Zuweisung gebildet wird:

  gv = "global"   # Globale Variable gv erzeugen
                  #
  def f(...):     # Funktion f() definieren
      print(gv)   # Globale Variable gv lesen (OK)
                  #
  def g(...):     # Funktion g() definieren
      gv = 123    # Lokale Variable gv erzeugen (OK, gleichnamig)
      print(gv)   # Lokale Variable gv lesen    (OK)

Erst eine globale Variable LESEN und dann eine gleichnamige lokale Variable
ERZEUGEN ist nicht erlaubt (erzeugt den Fehler "UnboundLocalError" mit der
Meldung "local variable 'gv' referenced before assignment):

  def g(...):     # Funktion g() definieren
      print(gv)   # Globale Variable gv lesen   (OK)
      gv = 123    # Lokale Variable gv erzeugen (PENG, gleichnamig)

Per Schlüsselwort "global" kann eine Funktion auf einen GLOBALEN Namen
des Hauptprogramms LESEND + SCHREIBEND zugreifen (diese "Schweinerei" wird
also durch das Schlüsselwort "global" deutlich sichtbar gemacht):

  gv = "global"       # Globale Variable gv erzeugen
                      #
  def f(...):         # Funktion f() definieren
      global gv       # Globale Variable gv lesen + schreiben erlauben
      print(gv)       # Globale Variable gv lesen     (OK)
      gv = "lokal"    # Globale Variable gv schreiben (OK)
      print(gv)       # Globale Variable gv lesen     (OK)

Per Schlüsselwort "nonlocal" kann eine Funktion auf einen LOKALEN Namen der
direkt umgebenden "Wrapper"-Funktion zugreifen (LESEN + SCHREIBEN). Dies ist
auch mehrfach bei mehrfach verschachtelten Funktionen möglich:

  def f(...):             # Funktion f() definieren
      lv = "lokal"        # Lokale Variable lv erzeugen
                          #
      def g(...):         # Eingeschachtelte (lokale) Funktion g() definieren
          nonlocal lv     # Nichtlokale Variable lv von f() verwenden
          print(lv)       # Nichtlokale Variable lv von f() lesen     (OK)
          lv = "lokal"    # Nichtlokale Variable lv von f() schreiben (OK)
          print(lv)       # Nichtlokale Variable lv von f() lesen     (OK)