Escopo de variáveis em Python e a regra LEGB

Resposta rápida: quando Python encontra um nome, procura nesta ordem: Local, Enclosing, Global e Built-in, origem da sigla LEGB. Uma atribuição dentro de uma função normalmente cria um nome local. Use parâmetros e retornos como padrão; reserve global para estado realmente compartilhado no módulo e nonlocal para alterar o estado de uma função externa.

Entender essa regra elimina boa parte dos NameError e UnboundLocalError e torna funções mais previsíveis. Escopo não é apenas “onde a variável existe”: ele define qual vínculo entre nome e objeto será usado em cada ponto do programa. Se funções ainda são um assunto novo, consulte também o guia de funções em Python.

Nome, objeto e escopo não são a mesma coisa

Em Python, uma variável é um nome associado a um objeto. A instrução quantidade = 3 cria ou atualiza o vínculo do nome quantidade no namespace atual. Duas variáveis podem apontar para o mesmo objeto, e trocar um vínculo não altera necessariamente o objeto anterior.

Um namespace é um mapeamento de nomes para objetos; escopo é a região do código em que um namespace pode ser consultado diretamente. Módulos, funções e classes criam namespaces, mas possuem detalhes de resolução diferentes. A documentação oficial sobre escopos e namespaces apresenta esse modelo formalmente.

taxa = 0.10

def calcular_total(preco):
    desconto = 5
    return preco * (1 + taxa) - desconto

print(calcular_total(100))  # 105.0

Durante a chamada, preco e desconto são locais; taxa vem do módulo. O código apenas lê taxa, portanto não precisa de global. Essa separação também ajuda a organizar código em módulos e pacotes sem depender de nomes acidentais.

Como a busca LEGB funciona

Python resolve cada uso de nome seguindo quatro níveis:

  • Local (L): parâmetros e nomes definidos na função atual.
  • Enclosing (E): escopos das funções externas que envolvem a função atual.
  • Global (G): namespace do módulo em que a função foi definida.
  • Built-in (B): nomes fornecidos por builtins, como len, sum e print.
rotulo = "global"

def externa():
    rotulo = "enclosing"

    def interna():
        rotulo = "local"
        return rotulo

    return interna()

print(externa())  # local

Remova a atribuição dentro de interna e o resultado será enclosing. Remova também a atribuição de externa e a busca chegará ao rotulo global. Se nenhum nível contiver o nome, ocorre NameError. Esse comportamento é detalhado no modelo de execução do Python.

Evite sombrear built-ins:

## Evite: list deixa de referenciar o construtor embutido.
list = [1, 2, 3]

## Prefira um nome descritivo.
numeros = [1, 2, 3]

Nomes como list, str, id, filter e input são válidos, mas dificultam chamadas posteriores e confundem leitores.

Escopo local e o UnboundLocalError

A classificação de um nome como local é feita ao compilar o corpo inteiro da função, não linha por linha. Se existe uma atribuição ao nome em qualquer ponto da função, ele é local, salvo declaração global ou nonlocal.

contador = 10

def incrementar():
    print(contador)
    contador += 1

incrementar()  # UnboundLocalError

Como contador += 1 é leitura seguida de atribuição, Python considera contador local. O print tenta lê-lo antes de o vínculo local existir. Uma solução explícita e geralmente melhor recebe e devolve o valor:

def incrementar(contador):
    return contador + 1

contador = incrementar(contador)

Parâmetros deixam dependências visíveis e simplificam testes. Eles também evitam efeitos colaterais, princípio útil tanto em código funcional quanto em programação orientada a objetos.

Quando usar global

global informa que atribuições feitas naquela função devem atingir o namespace do módulo. A declaração vale para o bloco inteiro e precisa aparecer antes do uso do nome no mesmo bloco.

chamadas = 0

def registrar_chamada():
    global chamadas
    chamadas += 1

O recurso é legítimo para casos pequenos, como uma configuração simples ou instrumentação controlada, mas estado global mutável cria acoplamento: a função passa a depender da ordem das chamadas. Em aplicações maiores, prefira retornar o novo valor, encapsular estado em um objeto ou injetar uma dependência.

global não cria uma variável “universal” entre módulos. Ele aponta para o módulo em que a função foi definida. A especificação completa está na documentação da instrução global.

Closures e nonlocal

Uma função interna pode conservar referências do escopo enclosing mesmo após a função externa terminar. Isso forma uma closure, assunto aprofundado no guia de closures em Python.

def criar_contador(inicial=0):
    valor = inicial

    def proximo():
        nonlocal valor
        valor += 1
        return valor

    return proximo

contador = criar_contador(10)
print(contador())  # 11
print(contador())  # 12

Sem nonlocal, a atribuição criaria um novo valor local em proximo e causaria UnboundLocalError na operação +=. nonlocal procura o vínculo em uma função externa próxima; não usa o namespace global e exige que o nome já exista em algum escopo enclosing.

Closures são úteis para manter estado pequeno e privado, criar fábricas de funções e implementar decoradores. Para casos mais elaborados, veja decoradores em Python. Se o estado tiver muitas operações ou invariantes, uma classe costuma ser mais clara.

Classes, comprehensions e outros limites

O corpo de uma classe possui namespace próprio, mas métodos não tratam esse namespace como escopo enclosing. Acesse atributos por self, pela classe ou com classmethod:

class Carrinho:
    limite = 20

    def aceita(self, quantidade):
        return quantidade <= self.limite

Usar apenas limite dentro do método não encontra automaticamente Carrinho.limite. Métodos seguem LEGB a partir de seu escopo local e do módulo.

Comprehensions em Python 3 também executam em escopo próprio. O nome de iteração não “vaza”:

quadrados = [numero ** 2 for numero in range(4)]
## numero não está definido aqui

Isso evita colisões, mas closures criadas em loops merecem atenção porque consultam o nome quando são chamadas:

funcoes = [lambda n=n: n * 2 for n in range(3)]
print([funcao() for funcao in funcoes])  # [0, 2, 4]

O parâmetro padrão captura o valor de cada iteração. Sem n=n, todas as lambdas observariam o valor final. O guia de lambda, map e filter explora aplicações e limites dessa sintaxe.

Erros comuns e como diagnosticá-los

Confundir mutação com atribuição: uma função pode mutar uma lista global sem global, pois o nome não é reassociado. Isso funciona, mas ainda é efeito colateral.

itens = []

def adicionar(item):
    itens.append(item)  # muta o objeto; não reassocia itens

Usar global para corrigir qualquer erro: a declaração pode esconder um desenho ruim. Primeiro pergunte se o valor deveria ser parâmetro, retorno ou atributo.

Esperar escopo de bloco: if, for, while, with e try não criam escopo local separado. Um nome atribuído neles continua no escopo da função ou módulo, embora talvez nunca seja criado se o ramo não executar.

Diagnosticar às cegas: locals() e globals() ajudam a inspecionar namespaces durante estudos e depuração. Não modifique seus resultados como mecanismo normal da aplicação; torne o fluxo de dados explícito.

Checklist final

  • Identifique em qual função ou módulo cada nome é atribuído.
  • Aplique LEGB e pare no primeiro vínculo encontrado.
  • Prefira parâmetros e retornos a estado global mutável.
  • Use nonlocal apenas para estado simples de uma closure.
  • Acesse atributos de classe explicitamente por self ou pela classe.
  • Não sombreie built-ins com nomes como list ou str.
  • Ao ver UnboundLocalError, procure uma atribuição posterior no mesmo bloco.

A regra LEGB fica simples quando você separa nome, objeto e namespace. Faça dependências aparecerem na assinatura das funções e trate global e nonlocal como ferramentas específicas, não atalhos. O resultado é código mais legível, testável e resistente a alterações.