Rób to, co możesz, tym, co posiadasz, i tam, gdzie jesteś.
Theodore Roosevelt
global and nonlocalHistorycznie od początku istnienia języka Python możliwe było powiązanie nazw
zmiennych jedynie w zakresie lokalnym - poprzez ich definicję oraz globalnie w
obrębie modułu z użyciem instrukcji global. Istniał jedynie zakres globalny i
lokalny. W wielu językach programowania jest to naturalny podział z uwagi na
brak możliwości definiowania zagnieżdżonych funkcji. Za przykład takiego języka
może posłużyć C. W Python natomiast problem ograniczenia do zakresu
lokalnego i globalnego dość mocno dawał się programistom we znaki. Ostatecznie
po wielu dyskusjach i propozycjach rozwiązań w składni języka pojawiła się
instrukcja nonlocal. Deklaracja zmiennej z jej użyciem zmieni zakres jej
widoczności do okalającym zakresie. Z rozwojem języka Python zmianom ulegał
zakres zmiennych. W tym artykule skupiam się jedynie na Python 3.X.
Wcześniejsze wersje zakończyły już oficjalne wsparcie i nie widze potrzeby
zajmowania się ich niuansami.
Wykonanie instrukcji x = 1 tworzy lub modyfikuje nazwę x jako zmienną
lokalną, po czym przypisuje do niej wartość 1. Zmienne lokalne są widoczne
tylko dla kodu z tego samego zasięgu. By zmienić zakres widoczności używamy
instrukcji global i nonlocal. 
By w móc w pełni rozmawiać o zakresie zmiennych konieczne jest operowanie się funkcjami zagnieżdżonymi. Funkcja zdefiniowana wewnątrz innej funkcji jest funkcją zagnieżdżoną. Przykład:
def outer(value):
    # Zewnętrzna fukcja zamykająca
    def inner():
        # Funkcja zagnieżdżona
        print(value)
    return inner
Na tym etapie musisz jedynie rozumieć czym jest funkcja zagnieżdżona. Więcej światła na niuanse funkcji zagnieżdżonych pojawi się w dalszej części artykułu.
globalZmienne zdefiniowane poza ciałem funkcji to zmienne globalne. Zmienne
globalne mogą być używane zarówno wewnątrz jak i na zewnątrz funkcji. Domyślnie
dostępne są w trybie tylko do odczytu. By umożliwić modyfikację zmiennej musi
zadeklarować ją z użyciem instrukcji global. Jeśli zmienna zostanie
zdefiniowana wewnątrz funkcji - bez użycia instrukcji global - uznawana jest
domyślnie za zmienną lokalną. Jest to ogólna zasada.
W języku Python wszystkie zmienne spoza lokalnego zakresu dostępne są w trybie
read-only. Dokonanie na nich modyfikacji wymaga wyraźnego zdeklarowania ich 
z użyciem nonlocal, bądź global. 
var = 0  # Global variable
def func_ro():
    print(var)  # var access is read-only
def func_rw()
    global var
    print(var)  # var access is read/write
Standardowym sposobem dzielenia informacji między modułami - w ramach jednego programu - jest utworzenie specjalnego modułu przechowującego współdzielone informacje. Moduł taki jest następnie importowany do wszystkich modułów w których informacje są wymagane. Istnieje tylko jedna instancja każdego modułu dlatego też wszelkie wprowadzane w nim zmiany będą dostępne wszędzie, gdzie został zaimportowany. Poniżej przykład modułu konfiguracyjnego.
# File: config.py
# Configuration module example
MODE = "TEST"  # Default value of the MODE configuration setting
Gdziekolwiek zostanie od zaimportowany umożliwi dostęp do zmiennej MODE, a jej
modyfikacja będzie widoczna globalnie.
nonlocalInstrukcji tej należy się kilka słów komentarza. nonlocal umożliwia ponowne
wiązanie zmiennych poza zakresem lokalnym lecz z wyłączeniem zakresu globalnego
(modułu). 
Użycie nonlocal powoduje, że oznaczone modyfikatorem identyfikatory
odwołują się do poprzednio zdefiniowanych zmiennych w najbliższym dostępnym
zasięgu. Przykładem zastosowania jest nadanie funkcji praw do modyfikacji
zmiennych okalającej funkcji zewnętrznej.
def sum():
    x = 0
    def add(y):
            nonlocal x
            x += y
            print(x)
    return add
WAŻNE Nazwy zmiennych wymienionych w instrukcji nonlocal nie mogą
kolidować z nazwami zmiennych zdefiniowanych a obecnym zakresie.  
s = "something"
def func()
    s = 'value'
    nonlocal s
    ^
SyntaxError: name 's' is assigned to before nonlocal declaration
W powyższym przykładzie w ciele funkcji została zadeklarowana i zdefiniowana
zmienna s po czym próbowaliśmy ją przesłonić zmienną swobodną. Tego typu
zabieg jest niedozwolony i wywołuje błąd składni. 
global oraz nonlocalDziałanie instrukcji global, oraz nonlocal najlepiej ilustrują poniższe
przykłady.
# Przykład bez zastosowania modyfikatorów zakresu -----------------------------
s = 'global' # Zmienna globalna 
def outer():
    s = 'outer' # Zmienna lokalna funkcji outer
    def inner():
        s = 'inner' # Zmienna lokalna funkcji inner
        print(f'Zakres inner, s={s}')
    inner()
    print(f'Zakres outer, s={s}')
outer()
print(f'Zakres global, s={s}')
""" Wynik działania:
Zakres inner, s=inner
Zakres outer, s=outer
Zakres global, s=global
"""
# Przykład z zastosowaniem modyfikatora 'global' ------------------------------
s = 'global' # Zmienna globalna 
def outer():
    s = 'outer' # Zmienna lokalna funkcji outer
    def inner():
        global s
        s = 'inner' # Zmienna lokalna funkcji inner
        print(f'Zakres inner, s={s}')
    inner()
    print(f'Zakres outer, s={s}')
outer()
print(f'Zakres global, s={s}')
""" Wynik działania:
Zakres inner, s=inner
Zakres outer, s=outer
Zakres global, s=inner
"""
# Przykład z zastosowaniem modyfikatora 'nonlocal' ----------------------------
s = 'global' # Zmienna globalna 
def outer():
    s = 'outer' # Zmienna lokalna funkcji outer
    def inner():
        nonlocal s
        s = 'inner' # Zmienna lokalna funkcji inner
        print(f'Zakres inner, s={s}')
    inner()
    print(f'Zakres outer, s={s}')
outer()
print(f'Zakres global, s={s}')
""" Wynik działania:
Zakres inner, s=inner
Zakres outer, s=inner
Zakres global, s=global
"""
W tym przypadku przykład wart jest więcej niż 1000 słów.
Zmienna lokalna to zmienna związana z lokalnym zakresem, natomiast  zmienna
swobodna to zmienna nie związana z lokalnym zakresem. Nazwy zmiennych
lokalnych, oraz swobodnych przechowywane są w atrybucie __code__ obiektu
funkcji. __code__ jest obiektem reprezentującym `skompilowany kod maszynowy
funkcji. 
Dowiedzmy się jak przechowywane są zmienne lokalne i swobodne w __code__. Za
przykład posłuży nam poniższy kod zawierający definicję jednej funkcji
zagnieżdżonej:
def sum():
    x = 0
    def add(y):
            nonlocal x
            x += y
            print(x)
    return add
func = sum()
del sum # Not needed anymore
func(3) # Variable x = 3
Prowadząc inspekcję skompilowanego kodu zauważymy, że zmienne lokalne, oraz
swobodne przechowywane są w różnych atrybutach __code__:
func.__code__.co_freevars   # Outputs ('x',)
func.__code__.co_varnames   # Outputs ('y',)
Wiązanie zmiennej x jest utrzymywane w atrybucie __closure__ obiektu
func. Każdemu elementowi tupli func.__code__.co_freevars odpowiada element
func.__closure__ przechowujący aktualną wartość zmiennej.
for idx, _ in enumerate(func.__closure__): 
    print(func.__closure__[idx].cell_contents) # prints only digit '3'
W powyższym przykładzie mamy jedynie jeden element w func.__closure__
ponieważ func ma tylko jedną zmienną swobodną.
Domknięcia są silnie związane z funkcjami zagnieżdżonymi. Domknięcie to
sytuacja w której dane spoza zakresu lokalnego funkcji są trwale dołączane do
maszynowego kodu funkcji. Zachowanie wiązań do zmiennych swobodnych jest 
wymagane by zmienne te mogły być używane w czasie wywołania funkcji, gdy zasięg
definiujący te zmienne nie jest już dostępny.
Możliwe to jest przy spełnieniu następujących warunków:
Innymi słowy domknięcie to funkcja ze stanem, funkcja przechowująca zmienne 
wartości znajdujące się poza jej zasięgiem.
def nth_power(exp):
    def power_of(base):
        return pow(base, exp)
    return power_of
W przykładzie występuje taka sytuacja. Funkcja power_of odnosi się do
zmiennej exp, która to nie należy do jej lokalnego zakresu.
Funkcja może mieć do czynienia ze zmiennymi swobodnymi zewnętrznymi nie będącymi zmiennymi globalnymi jedynie, gdy funkcja ta jest zagnieżdżona w innej funkcji.
Najczęściej domknięcia wykorzystywane są przez dekoratory, lecz nie jest to
ich jedyne użycie. 
Mając do zaimplementowania klasę z jedną metodą, domknięcia zapewniają alternatywne rozwiązanie problemu. Natomiast przy dużej ilości metod i atrybutów lepiej użyć klasy.
Pozwalają również na uniknięcie definiowania zmiennych globalnych, zdefiniowanie stałych, oraz zapewniają pewien rodzaj ukrywania danych.
def increment_by(v1):
    def increment(v2):
        return v1 + v2
    return increment
increment_by_2 = increment_by(2)
del increment_by
print(increment_by_2(5))    # returns 7
W powyższym przykładzie zmienna v1 została na stałe dodana do kodu maszynowego
funcji increment_by_2. Nie mamy do niej bezpośredniego dostępu, lecz jest ona
dostępna pomimo iż zakres fukcji w której została zdefiniowana increment_by
nie istnieje.
Liczę na to, że artykuł ten pomoże Ci w przyszłości świadomie operować zakresem zmiennych umieszczanych w kodzie.