Escopo de variáveis: local, global e nonlocal

1. Introdução ao conceito de escopo em Python

Escopo de variável define a região do código onde uma variável pode ser acessada. Em Python, o escopo determina quais nomes estão disponíveis em cada parte do programa, evitando conflitos e garantindo previsibilidade. Sem regras claras de escopo, variáveis poderiam ser alteradas acidentalmente em qualquer lugar, gerando bugs difíceis de rastrear.

Python segue a regra LEGB (Local, Enclosing, Global, Built-in) para resolver nomes em tempo de execução:

  1. Local — dentro da função atual
  2. Enclosing — funções externas aninhadas (closures)
  3. Global — nível do módulo
  4. Built-in — nomes embutidos como print, len, range

Quando você usa uma variável, o Python percorre essa hierarquia até encontrar o nome ou lançar um NameError.

2. Escopo local: variáveis dentro de funções

Variáveis definidas dentro de uma função são locais — existem apenas durante a execução da função e são destruídas ao final.

def calcular_area(base, altura):
    area = base * altura  # 'area' é local à função
    return area

resultado = calcular_area(5, 3)
print(resultado)  # 15
# print(area)  # NameError: name 'area' is not defined

Parâmetros de função também são variáveis locais:

def saudacao(nome):
    mensagem = f"Olá, {nome}!"  # 'nome' e 'mensagem' são locais
    return mensagem

O ciclo de vida de uma variável local começa quando a função é chamada e termina quando ela retorna — cada chamada cria um novo escopo local independente.

3. Escopo global: variáveis no módulo

Variáveis definidas fora de qualquer função pertencem ao escopo global do módulo:

contador = 0  # variável global

def incrementar():
    global contador  # declara que vamos usar a variável global
    contador += 1

incrementar()
incrementar()
print(contador)  # 2

Sem a palavra-chave global, o Python criaria uma nova variável local contador dentro da função, ignorando a global. O uso de global deve ser cauteloso — alterar variáveis globais dentro de funções gera efeitos colaterais que prejudicam a legibilidade e a testabilidade do código.

# Exemplo problemático
total = 0

def adicionar(valor):
    global total
    total += valor  # efeito colateral: modifica estado global
    return total

# Melhor abordagem: receber e retornar valores
def adicionar_puro(total_atual, valor):
    return total_atual + valor

4. A palavra-chave nonlocal e escopos aninhados

Funções definidas dentro de outras funções criam escopos aninhados. A palavra-chave nonlocal permite modificar variáveis do escopo intermediário (enclosing):

def fabricar_contador():
    total = 0  # variável no escopo enclosing

    def incrementar():
        nonlocal total  # modifica a variável do escopo externo
        total += 1
        return total

    return incrementar

contador1 = fabricar_contador()
print(contador1())  # 1
print(contador1())  # 2

contador2 = fabricar_contador()
print(contador2())  # 1 (cada closure tem seu próprio 'total')

A diferença entre nonlocal e global:

  • global refere-se ao escopo do módulo (nível mais alto)
  • nonlocal refere-se ao escopo imediatamente externo (pode haver vários níveis de aninhamento)
def nivel_externo():
    x = "externo"

    def nivel_medio():
        x = "médio"  # sombreia o 'x' externo

        def nivel_interno():
            nonlocal x  # modifica o 'x' do nível médio
            x = "modificado"

        nivel_interno()
        print(x)  # "modificado"

    nivel_medio()
    print(x)  # "externo" (não foi modificado)

nivel_externo()

5. Sombreamento (shadowing) e conflitos de nomes

Sombreamento ocorre quando uma variável local tem o mesmo nome de uma variável global, "escondendo" a global:

mensagem = "Global"

def mostrar():
    mensagem = "Local"  # sombreia a variável global
    print(mensagem)     # "Local"

mostrar()
print(mensagem)  # "Global" (a global não foi alterada)

Problemas comuns de shadowing:

def calcular_desconto(preco, desconto=0.1):
    preco = preco * (1 - desconto)  # 'preco' sombreia o parâmetro original
    return preco

# Melhor prática: usar nomes distintos
def calcular_desconto(preco_original, desconto=0.1):
    preco_final = preco_original * (1 - desconto)
    return preco_final

6. Escopo em estruturas de controle (loops, condicionais)

Diferente de linguagens como C ou Java, Python não cria novo escopo em blocos if, for ou while. Variáveis definidas dentro dessas estruturas "vazam" para o escopo externo:

for i in range(5):
    ultimo_valor = i

print(i)             # 4 (vazou!)
print(ultimo_valor)  # 4

# Em Python, 'i' continua existindo após o loop

Isso pode causar surpresas:

def encontrar_indice(lista, alvo):
    for indice, valor in enumerate(lista):
        if valor == alvo:
            break
    # 'indice' ainda existe aqui se o break foi executado
    return indice  # NameError se alvo não estiver na lista

# Solução: inicializar antes do loop
def encontrar_indice_seguro(lista, alvo):
    indice = -1
    for i, valor in enumerate(lista):
        if valor == alvo:
            indice = i
            break
    return indice

7. Built-in scope: o escopo embutido do Python

O escopo built-in contém funções e exceções padrão como print, len, int, ValueError. É o último nível na hierarquia LEGB:

# Python procura: local -> enclosing -> global -> built-in
print(len("teste"))  # 'len' vem do escopo built-in

Sobrescrever nomes built-in acidentalmente é um erro comum:

# Cuidado! Sobrescreveu 'list' do built-in
list = [1, 2, 3]
print(list)  # Funciona, mas...

# Agora não podemos mais usar list() como construtor
# nova_lista = list([4, 5, 6])  # TypeError: 'list' object is not callable

Para ver todos os nomes built-in:

print(dir(__builtins__))

8. Boas práticas e dicas finais

Prefira variáveis locais sempre que possível — elas tornam o código mais previsível, testável e reutilizável.

Evite global e nonlocal em código extenso — use parâmetros e retornos para passar dados entre funções.

Use closures com nonlocal para encapsulamento elegante:

def criar_cache():
    cache = {}  # encapsulado na closure

    def obter(chave, funcao_calculo):
        if chave not in cache:
            cache[chave] = funcao_calculo()
        return cache[chave]

    return obter

cache_consulta = criar_cache()
resultado = cache_consulta("usuario_123", lambda: {"nome": "Maria"})

Nomeie variáveis com clareza para evitar shadowing acidental:

# Ruim
def processar(nome):
    nome = nome.upper()  # sombreia o parâmetro

# Bom
def processar(nome_original):
    nome_processado = nome_original.upper()

Lembre-se: escopo não é apenas uma regra técnica — é uma ferramenta de design que ajuda a organizar o código e controlar o acesso aos dados. Use-a com intenção e seu código ficará mais limpo, seguro e fácil de manter.

Referências