Da trilha de Python Este conceito ainda não saiu como card do dia. ir para o card de hoje

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.
  • global permite modificar uma variável do módulo; nonlocal permite 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: ...) ou functools.partial.
escopo_closures.py
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

Exercícios

  1. 1
    Fácil

    Modificando variável global

    Crie uma função adiciona_pontos(pontos) que some pontos a uma variável global total e retorne o novo valor. Use a declaração global.

    Resolver
  2. 2
    Médio

    Fábrica de multiplicadores com closure

    Crie uma função cria_multiplicador(fator) que retorne uma closure. A closure deve receber um número e retornar o produto pelo fator. Use uma closure real (função interna que captura fator).

    Resolver
  3. 3
    Desafio

    Evitando late binding em closures de loop

    Dada uma lista de funções criadas em um loop que deveriam retornar seus índices, corrija o problema de late binding para que cada função retorne o valor correto do índice. Implemente cria_funcoes(n) que retorna uma lista de n funções, onde a função de índice i retorna i.

    Resolver

Continue estudando