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, comolen,sumeprint.
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
nonlocalapenas para estado simples de uma closure. - Acesse atributos de classe explicitamente por
selfou pela classe. - Não sombreie built-ins com nomes como
listoustr. - 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.