Python Funções Intermediário
Escopo e closures
Em Python, escopo define onde uma variável pode ser acessada. A regra LEGB (Local, Enclosing, Global, Built-in) determina a ordem de busca de um nome: primeiro no escopo local, depois nos escopos externos (funções aninhadas), depois no módulo global e por fim nos nomes embutidos (len, print, etc.).
Quando você atribui a uma variável dentro de uma função, Python a considera local por padrão. Para modificar uma variável global, use global; para modificar uma variável do escopo externo (mas não global), use nonlocal. Sem essas declarações, a atribuição cria uma nova variável local e a externa permanece inalterada.
Uma closure é uma função interna que "lembra" o ambiente onde foi criada, mesmo depois que a função externa terminou. Isso permite criar fábricas de funções e estados encapsulados. Porém, closures capturam variáveis por referência, não por valor: se a variável externa mudar depois, a closure verá o novo valor. Esse comportamento é chamado de late binding e costuma aparecer em loops com lambdas ou funções definidas dentro de for.
Pontos-chave
- A ordem de busca de nomes é LEGB: Local → Enclosing → Global → Built-in.
globalpermite modificar uma variável do módulo;nonlocalpermite modificar uma variável do escopo externo (função aninhada).- Closures capturam variáveis do escopo externo e mantêm o estado mesmo após a função externa retornar.
- Late binding: closures e lambdas capturam variáveis por referência, então o valor final é o que estiver na variável no momento da chamada.
- Para evitar late binding em loops, use argumentos padrão (
lambda x=i: ...) oufunctools.partial.
def contador():
"""Retorna uma closure que incrementa e devolve um contador."""
n = 0
def incrementa():
nonlocal n # modifica n do escopo externo
n += 1
return n
return incrementa
c1 = contador()
c2 = contador()
print(c1(), c1(), c1()) # 1 2 3
print(c2()) # 1 (estado independente)
# Late binding: todas as lambdas usam o i final do loop
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]
# Correção com argumento padrão (captura por valor)
funcs_ok = [lambda i=i: i for i in range(3)]
print([f() for f in funcs_ok]) # [0, 1, 2]
# global: modifica a variável do módulo
contador_global = 0
def incrementa_global():
global contador_global
contador_global += 1
incrementa_global()
print(contador_global) # 1