Python & AI Tutorials Logo
Programação Python

21. Escopo de Variáveis e Resolução de Nomes

Quando você cria uma variável em Python, onde ela "vive"? Uma função consegue ver variáveis criadas fora dela? Um código fora de uma função consegue acessar variáveis criadas dentro dela? Essas perguntas são sobre escopo (scope) — a região do seu programa em que um nome é visível e pode ser usado.

Entender escopo é crucial para escrever funções que funcionem corretamente e de forma previsível. Sem esse conhecimento, você pode acabar criando bugs acidentalmente, em que variáveis não têm os valores que você espera, ou em que mudanças em variáveis não persistem como você pretendia.

Neste capítulo, vamos explorar como o Python determina a qual variável um nome se refere, como controlar onde variáveis ficam acessíveis e o que acontece quando você apaga um nome. No final, você vai entender as regras que governam a visibilidade de variáveis em programas Python.

21.1) Variáveis Locais e Globais

Toda variável em Python existe dentro de um escopo (scope) específico — uma região de código em que aquele nome de variável é definido e acessível. Os dois escopos mais fundamentais são local e global.

Entendendo o Escopo Global

Variáveis criadas no nível superior do seu programa — fora de qualquer função — existem no escopo global. Elas são chamadas de variáveis globais, e ficam acessíveis de qualquer lugar no seu módulo depois de serem definidas.

python
# Variável global - definida no nível do módulo
total_users = 0
 
def show_user_count():
    # Esta função pode LER a variável global
    print(f"Total users: {total_users}")
 
show_user_count()  # Output: Total users: 0
print(total_users)  # Output: 0

Neste exemplo, total_users é uma variável global. Tanto a função show_user_count() quanto o código no nível do módulo podem acessá-la. Pense em variáveis globais como visíveis ao longo de todo o seu arquivo de programa.

Entendendo o Escopo Local

Variáveis criadas dentro de uma função existem no escopo local daquela função. Elas são chamadas de variáveis locais, e só ficam acessíveis dentro da função em que foram definidas. Assim que a função termina de executar, as variáveis locais desaparecem.

python
def calculate_discount(price):
    # discount_rate é LOCAL a esta função
    discount_rate = 0.15
    discount_amount = price * discount_rate
    return discount_amount
 
result = calculate_discount(100)
print(result)  # Output: 15.0
 
# Isto causaria um erro - discount_rate não existe aqui
# print(discount_rate)  # NameError: name 'discount_rate' is not defined

As variáveis discount_rate e discount_amount existem apenas enquanto calculate_discount() está rodando. Depois que a função retorna, esses nomes não existem mais. Isso, na verdade, é uma coisa boa — impede que funções deixem seu programa cheio de variáveis temporárias.

Por Que o Escopo Local Importa

O escopo local fornece encapsulamento — cada função tem seu próprio espaço de trabalho privado. Isso significa que você pode usar os mesmos nomes de variáveis em funções diferentes sem conflitos:

python
def calculate_tax(amount):
    rate = 0.08  # Variável local
    return amount * rate
 
def calculate_shipping(weight):
    rate = 5.00  # Variável local diferente com o mesmo nome
    return weight * rate
 
tax = calculate_tax(100)
shipping = calculate_shipping(3)
 
print(f"Tax: ${tax}")         # Output: Tax: $8.0
print(f"Shipping: ${shipping}")  # Output: Shipping: $15.0

As duas funções usam uma variável chamada rate, mas elas são variáveis completamente separadas em escopos locais diferentes. Mudanças em rate em uma função não afetam rate na outra função. Esse isolamento torna as funções mais confiáveis e mais fáceis de entender.

Lendo Variáveis Globais a Partir de Funções

Funções podem ler variáveis globais sem nenhuma sintaxe especial:

python
# Configuração global
max_login_attempts = 3
 
def check_login(password):
    # Lendo variável global
    if password == "secret123":
        return "Login successful"
    else:
        return f"Invalid password. You have {max_login_attempts} attempts."
 
result = check_login("wrong")
print(result)  # Output: Invalid password. You have 3 attempts.

A função check_login() consegue ler max_login_attempts porque é uma variável global. Porém, há uma limitação importante que precisamos entender.

A Regra: Atribuição Cria Variáveis Locais

Aqui é onde escopo fica mais complicado. Se você faz uma atribuição a um nome de variável dentro de uma função, o Python cria uma nova variável local com esse nome, mesmo que exista uma variável global com o mesmo nome:

python
counter = 0  # Variável global
 
def increment_counter():
    # AVISO: Isto cria uma NOVA variável local chamada counter - apenas para demonstração
    # PROBLEMA: Tentando ler counter antes de atribuir a ela localmente
    counter = counter + 1  # UnboundLocalError: local variable 'counter' referenced before assignment
    print(counter)
 
# increment_counter() # Esta chamada resulta em UnboundLocalError

Esse código falha porque o Python vê a atribuição counter = counter + 1 e decide que counter deve ser uma variável local. Mas então, quando ele tenta avaliar counter + 1, a variável local counter ainda não tem um valor — estamos tentando usá-la antes de atribuir um valor a ela.

Essa é uma fonte comum de confusão. A regra é: se uma função atribui a um nome de variável em qualquer lugar do seu corpo, esse nome é tratado como local ao longo de toda a função, mesmo antes da atribuição.

Vamos ver isso de forma mais clara:

python
message = "Hello"  # Variável global
 
def show_message():
    print(message)  # Isto funciona - apenas lendo a global
    
def change_message():
    # AVISO: Isto demonstra um erro comum - apenas para demonstração
    # PROBLEMA: O Python vê a atribuição abaixo, então message é tratada como local em toda a função
    print(message)  # UnboundLocalError!
    message = "Goodbye"  # Isso torna message local para a função INTEIRA
 
show_message()  # Output: Hello
# change_message()  # Esta chamada resulta em UnboundLocalError

A função show_message() funciona bem porque ela só lê message. Mas change_message() falha porque a atribuição na segunda linha faz o Python tratar message como local ao longo de toda a função, incluindo o print() que vem antes da atribuição.

Parâmetros São Variáveis Locais

Parâmetros de função são variáveis locais que recebem seus valores iniciais a partir dos argumentos passados quando a função é chamada:

python
def greet(name):  # 'name' é uma variável local
    greeting = f"Hello, {name}!"  # 'greeting' também é local
    return greeting
 
message = greet("Alice")
print(message)  # Output: Hello, Alice!
 
# Nem 'name' nem 'greeting' existem aqui
# print(name)  # NameError

O parâmetro name existe apenas dentro da função greet(). Ele é criado quando a função é chamada e desaparece quando a função retorna.

Exemplo Prático: Cálculo de Carrinho de Compras

Vamos ver como o escopo local e global trabalham juntos em um cenário realista:

python
# Configuração global
tax_rate = 0.08
free_shipping_threshold = 50
 
def calculate_total(subtotal):
    # Variáveis locais para este cálculo
    tax = subtotal * tax_rate  # Lendo tax_rate global
    
    # Determinar custo de frete
    if subtotal >= free_shipping_threshold:  # Lendo o limiar global
        shipping = 0
    else:
        shipping = 5.99
    
    total = subtotal + tax + shipping
    return total
 
# Calcular para diferentes valores de carrinho
cart1 = calculate_total(30)
cart2 = calculate_total(60)
 
print(f"Cart 1 total: ${cart1:.2f}")  # Output: Cart 1 total: $38.39
print(f"Cart 2 total: ${cart2:.2f}")  # Output: Cart 2 total: $64.80

Neste exemplo:

  • tax_rate e free_shipping_threshold são valores globais de configuração
  • subtotal, tax, shipping e total são locais a cada chamada de calculate_total()
  • Cada chamada da função recebe seu próprio conjunto separado de variáveis locais
  • A função pode ler a configuração global, mas não a modifica

Essa separação de responsabilidades deixa o código claro: variáveis globais guardam configurações que se aplicam em todo lugar, enquanto variáveis locais guardam resultados temporários de cálculo específicos a cada chamada da função.

21.2) A Regra LEGB para Resolução de Nomes

Quando o Python encontra um nome de variável, como ele sabe a qual variável você está se referindo? O Python segue uma ordem específica de busca chamada regra LEGB (LEGB rule). LEGB significa Local, Enclosing, Global, Built-in — os quatro escopos que o Python procura, nessa ordem.

Os Quatro Escopos em LEGB

Vamos entender cada escopo na hierarquia LEGB:

  1. Local (L): o escopo da função atual
  2. Enclosing (E): o escopo de quaisquer funções envolventes (funções que contêm a função atual)
  3. Global (G): o escopo no nível do módulo
  4. Built-in (B): os nomes embutidos do Python, como print, len, int, etc.

Quando você usa um nome de variável, o Python procura nesses escopos em ordem: L → E → G → B. Ele usa a primeira correspondência que encontrar e para de procurar.

Escopo Local: O Primeiro Lugar em que o Python Procura

O Python sempre verifica primeiro o escopo local:

python
def calculate_price():
    price = 100  # Variável local
    tax = 0.08   # Variável local
    total = price * (1 + tax)
    return total
 
result = calculate_price()
print(result)  # Output: 108.0

Quando o Python vê price, tax e total dentro de calculate_price(), ele encontra esses nomes no escopo local e usa esses valores. A busca para no escopo local — o Python não precisa olhar além.

Escopo Global: Quando o Local Não Tem

Se um nome não for encontrado localmente, o Python verifica o escopo global:

python
# Variáveis globais
default_tax_rate = 0.08
default_currency = "USD"
 
def calculate_price(amount):
    # 'amount' é local, encontrado imediatamente
    # 'default_tax_rate' não é local, encontrado no escopo global
    total = amount * (1 + default_tax_rate)
    return total
 
result = calculate_price(100)
print(result)  # Output: 108.0

Quando o Python encontra default_tax_rate dentro da função, ele não acha localmente, então procura no escopo global e encontra lá.

Escopo Built-in: Nomes Predefinidos do Python

Se um nome não for encontrado no escopo local nem global, o Python verifica o escopo built-in — os nomes que o Python fornece automaticamente:

python
def process_data(numbers):
    # 'numbers' é local
    # 'len' não é local nem global - é built-in
    count = len(numbers)
    
    # 'max' também é built-in
    maximum = max(numbers)
    
    return count, maximum
 
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result)  # Output: (5, 30)

Os nomes len e max não são definidos no seu código — eles são funções built-in que o Python fornece. Quando o Python não encontra esses nomes localmente nem globalmente, ele verifica o escopo built-in e os encontra lá.

Escopo Envolvente (Enclosing): Funções Aninhadas

O escopo envolvente entra em cena quando você tem funções aninhadas — funções definidas dentro de outras funções. É aqui que o "E" em LEGB fica importante:

python
def outer_function():
    outer_var = "I'm from outer"  # No escopo envolvente para inner_function
    
    def inner_function():
        inner_var = "I'm from inner"  # Local a inner_function
        # inner_function consegue ver tanto inner_var (local) quanto outer_var (envolvente)
        print(inner_var)   # Output: I'm from inner
        print(outer_var)   # Output: I'm from outer
    
    inner_function()
 
outer_function()

Para inner_function(), o escopo de outer_function() é um escopo envolvente. Quando inner_function() referencia outer_var, o Python procura:

  1. Escopo local de inner_function() — não encontrado
  2. Escopo envolvente de outer_function() — encontrado! Usa esse valor

LEGB em Ação: Exemplo Simples

Vamos ver os quatro escopos funcionando juntos em um exemplo claro e direto:

python
# Built-in: len (o Python fornece isso)
# Global: multiplier
multiplier = 10
 
def outer(x):
    # Escopo envolvente para inner
    y = 5
    
    def inner(z):
        # Escopo local
        # z é local (L)
        # y vem do escopo envolvente (E)
        # multiplier vem do escopo global (G)
        # len vem do escopo built-in (B)
        result = len([z, y, multiplier])  # Usa todos os quatro escopos!
        return z + y + multiplier
 
    return inner(3)
 
answer = outer(100)
print(answer)  # Output: 18

Quando o Python avalia z + y + multiplier dentro de inner():

  1. L (Local): encontra z = 3
  2. E (Enclosing): encontra y = 5 em outer()
  3. G (Global): encontra multiplier = 10
  4. B (Built-in): encontra a função len

Este exemplo demonstra claramente como o Python procura por todos os quatro escopos para resolver nomes.

Shadowing: Quando Escopos Internos Escondem Nomes Externos

Se o mesmo nome existir em múltiplos escopos, o escopo mais interno "vence" — isso se chama shadowing:

python
value = "global"
 
def outer():
    value = "enclosing"
    
    def inner():
        value = "local"
        print(value)  # Qual value?
    
    inner()
    print(value)  # Qual value?
 
outer()
print(value)  # Qual value?

Output:

local
enclosing
global

Cada print() vê um value diferente porque o Python para na primeira correspondência:

  • Dentro de inner(): encontra value localmente → imprime "local"
  • Dentro de outer() mas fora de inner(): encontra value no escopo de outer() → imprime "enclosing"
  • No nível do módulo: encontra value globalmente → imprime "global"

Visualizando a Ordem de Busca LEGB

Sim

Não

Sim

Não

Sim

Não

Sim

Não

Referência de Nome

Encontrado no Local?

Usar Valor Local

Encontrado no Envolvente?

Usar Valor do Envolvente

Encontrado no Global?

Usar Valor Global

Encontrado no Built-in?

Usar Valor Built-in

NameError

Este diagrama mostra o processo de busca do Python. Ele começa no escopo mais interno e vai para fora. Se o nome não for encontrado em nenhum escopo, o Python levanta um NameError.

Por Que LEGB Importa ao Escrever Funções

Entender LEGB ajuda você a:

  1. Prever valores de variáveis: você sabe exatamente qual variável o Python vai usar
  2. Evitar conflitos de nomes: você entende quando nomes fazem shadowing entre si
  3. Projetar funções melhores: você consegue decidir qual escopo é apropriado para cada variável
  4. Depurar problemas de escopo: quando variáveis não têm os valores esperados, você pode rastrear pelo LEGB

A regra LEGB é fundamental para como o Python resolve nomes. Toda vez que você usa uma variável, o Python está seguindo essa regra por trás dos panos.

21.3) Usando a Palavra-chave global com Cuidado

Vimos que funções podem ler variáveis globais, mas e se você precisar modificar uma variável global de dentro de uma função? É aí que entra a palavra-chave global — mas ela deve ser usada com moderação e cuidado.

O Problema: Atribuição Cria Variáveis Locais

Como aprendemos antes, atribuir a uma variável dentro de uma função cria uma variável local:

python
counter = 0  # Variável global
 
def increment():
    # AVISO: Isto cria uma NOVA variável local chamada counter - apenas para demonstração
    # PROBLEMA: Tentando ler counter antes de atribuir a ela localmente
    counter = counter + 1  # UnboundLocalError!
    
# increment()  # Esta chamada resulta em UnboundLocalError

Isso falha porque o Python vê a atribuição e trata counter como local ao longo de toda a função. Mas estamos tentando ler counter antes de atribuir um valor a ela localmente.

Este é um dos erros mais comuns ao trabalhar com variáveis globais. A mensagem de erro UnboundLocalError: local variable 'counter' referenced before assignment diz exatamente o que aconteceu: o Python decidiu que counter era local (por causa da atribuição), mas você tentou usá-la antes de dar um valor a ela.

A Solução: Declarando Variáveis como Globais

A palavra-chave global diz ao Python: "Não crie uma nova variável local com este nome. Use a variável global em vez disso."

python
counter = 0  # Variável global
 
def increment():
    global counter  # Dizer ao Python para usar o counter global
    counter = counter + 1  # Agora isto modifica a variável global
 
print(f"Before: {counter}")  # Output: Before: 0
increment()
print(f"After: {counter}")   # Output: After: 1
increment()
print(f"After again: {counter}")  # Output: After again: 2

A declaração global counter precisa vir antes de você usar a variável. Ela diz ao Python que qualquer atribuição a counter nesta função deve modificar a variável global, e não criar uma local.

Múltiplas Variáveis Globais

Você pode declarar múltiplas variáveis como globais em uma única instrução:

python
total_sales = 0
total_customers = 0
 
def record_sale(amount):
    global total_sales, total_customers
    total_sales += amount
    total_customers += 1
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $0, Customers: 0
 
record_sale(25.50)
record_sale(30.00)
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $55.5, Customers: 2

Tanto total_sales quanto total_customers são declaradas como globais, então a função pode modificar as duas.

Quando Usar global: Estado Compartilhado

A palavra-chave global é apropriada quando você precisa manter estado compartilhado — dados que múltiplas funções precisam acessar e modificar:

python
# Estado do jogo
player_score = 0
player_lives = 3
game_over = False
 
def award_points(points):
    global player_score
    player_score += points
    print(f"Score: {player_score}")
 
def lose_life():
    global player_lives, game_over
    player_lives -= 1
    print(f"Lives remaining: {player_lives}")
    
    if player_lives <= 0:
        game_over = True
        print("Game Over!")
 
def check_game_status():
    # Apenas lendo globais - não precisa da palavra-chave global
    if game_over:
        return "Game Over"
    else:
        return f"Playing - Score: {player_score}, Lives: {player_lives}"
 
# Jogar o jogo
award_points(100)    # Output: Score: 100
award_points(50)     # Output: Score: 150
lose_life()          # Output: Lives remaining: 2
print(check_game_status())  # Output: Playing - Score: 150, Lives: 2

Este exemplo mostra um uso apropriado de global: múltiplas funções precisam modificar o estado compartilhado do jogo. Porém, observe que check_game_status() não precisa de global porque ela só lê as variáveis.

Por Que global Deve Ser Usado com Cuidado

Embora global às vezes seja necessário, usar demais pode deixar o código mais difícil de entender e manter. Eis o porquê:

Problema 1: Dependências Ocultas

Quando funções modificam variáveis globais, não fica óbvio pela chamada da função o que está mudando:

python
total = 0
 
def add_to_total(value):
    global total
    total += value
 
# O que esta função faz? Você não consegue saber sem ler o código dela
add_to_total(10)

Compare isso com uma função que retorna um valor:

python
def add_to_total(current_total, value):
    return current_total + value
 
total = 0
total = add_to_total(total, 10)  # Claro: total está sendo atualizado

A segunda versão deixa explícito que total está sendo modificado.

Problema 2: Testar Fica Mais Difícil

Funções que modificam estado global são mais difíceis de testar porque você precisa preparar e redefinir variáveis globais:

python
# Difícil de testar - depende de estado global
score = 0
 
def add_score(points):
    global score
    score += points
 
# Cada teste precisa resetar score
# Teste 1
score = 0
add_score(10)
assert score == 10
 
# Teste 2 - precisa resetar score de novo
score = 0
add_score(20)
assert score == 20

Problema 3: Funções Não São Reutilizáveis

Funções que dependem de variáveis globais específicas não podem ser facilmente reutilizadas em outros programas:

python
# Esta função só funciona se existir uma variável global chamada 'inventory'
inventory = []
 
def add_item(item):
    global inventory
    inventory.append(item)

Alternativas Melhores ao global

Em muitos casos, você pode evitar global usando valores de retorno e parâmetros:

Em vez de modificar estado global:

python
# Usando global (menos ideal)
balance = 1000
 
def withdraw(amount):
    global balance
    if amount <= balance:
        balance -= amount
        return True
    return False
 
withdraw(100)
print(balance)  # Output: 900

Use valores de retorno:

python
# Usando valores de retorno (melhor)
def withdraw(balance, amount):
    if amount <= balance:
        return balance - amount, True
    return balance, False
 
balance = 1000
balance, success = withdraw(balance, 100)
print(balance)  # Output: 900

A segunda versão é mais flexível, testável e reutilizável.

Quando global É Realmente Apropriado

Existem usos legítimos para global:

  1. Configuração que realmente precisa ser global:
python
# Configurações para toda a aplicação
debug_mode = False
log_level = "INFO"
 
def enable_debug():
    global debug_mode, log_level
    debug_mode = True
    log_level = "DEBUG"
  1. Contadores para debugging ou estatísticas:
python
# Rastrear chamadas de função para debugging
_function_call_count = 0
 
def tracked_function():
    global _function_call_count
    _function_call_count += 1
    # ... resto da função

Principais Lições Sobre global

  • Use global apenas quando você realmente precisa modificar o estado no nível do módulo
  • Prefira retornar valores e usar parâmetros em vez disso
  • Quando você usar global, documente por que isso é necessário
  • Considere se o seu design poderia ser melhorado para evitar global
  • Lembre-se: ler variáveis globais não exige a palavra-chave global — apenas modificá-las exige

21.4) Usando nonlocal para Modificar Variáveis em Funções Aninhadas

Quando você tem funções aninhadas, você pode precisar modificar uma variável do escopo de uma função envolvente. A palavra-chave nonlocal serve para isso — é como global, mas para escopos de funções envolventes em vez do escopo global.

O Problema: Modificar Variáveis Enclosing

Assim como atribuição cria variáveis locais por padrão, o mesmo problema acontece com escopos envolventes:

python
def outer():
    count = 0  # Variável no escopo de outer
    
    def inner():
        # AVISO: Isto cria uma NOVA variável local chamada count - apenas para demonstração
        # PROBLEMA: Tentando ler count antes de atribuir a ela localmente
        count = count + 1  # UnboundLocalError!
        print(count)
    
    inner()
 
# outer()  # Esta chamada resulta em UnboundLocalError

O Python vê a atribuição a count em inner() e a trata como uma variável local. Mas estamos tentando lê-la antes de atribuir um valor localmente, causando um erro.

A Solução: A Palavra-chave nonlocal

A palavra-chave nonlocal diz ao Python: "Esta variável não é local — procure por ela no escopo da função envolvente e use aquela."

python
def outer():
    count = 0  # Variável no escopo de outer
    
    def inner():
        nonlocal count  # Usar o count do escopo de outer
        count = count + 1
        print(f"Count in inner: {count}")
    
    print(f"Count before: {count}")  # Output: Count before: 0
    inner()                          # Output: Count in inner: 1
    print(f"Count after: {count}")   # Output: Count after: 1
 
outer()

Agora inner() consegue modificar a variável count do escopo de outer(). A mudança persiste depois que inner() retorna porque estamos modificando a variável real no escopo envolvente.

Por Que nonlocal É Útil: Funções Que Lembram Estado

A palavra-chave nonlocal permite um padrão poderoso em que funções internas conseguem manter e modificar estado do seu escopo envolvente. Vamos aprender sobre closures e funções fábrica em detalhes no Capítulo 23, mas por enquanto, entenda que nonlocal permite que funções internas modifiquem variáveis de escopos envolventes.

Aqui vai um exemplo simples mostrando como nonlocal funciona:

python
def create_counter():
    count = 0  # Esta variável está no escopo envolvente para increment
    
    def increment():
        nonlocal count  # Modificar o count do escopo envolvente
        count += 1
        return count
    
    return increment  # Retornar a função interna
 
# Criar um contador
counter1 = create_counter()
 
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
# Criar outro contador independente
counter2 = create_counter()
 
print(counter2())  # Output: 1
print(counter2())  # Output: 2

Cada chamada a create_counter() cria uma nova variável count e uma nova função increment() que consegue modificar aquele count específico usando nonlocal.

nonlocal vs global

É importante entender a diferença:

python
x = "global"
 
def outer():
    x = "enclosing"
    
    def use_global():
        global x  # Refere-se ao x global
        print(f"use_global sees: {x}")  # Output: use_global sees: global
    
    def use_nonlocal():
        nonlocal x  # Refere-se ao x de outer
        print(f"use_nonlocal sees: {x}")  # Output: use_nonlocal sees: enclosing
    
    use_global()
    use_nonlocal()
 
outer()
  • global sempre se refere ao escopo no nível do módulo
  • nonlocal se refere ao escopo da função envolvente mais próxima

Quando Você Não Pode Usar nonlocal

A palavra-chave nonlocal só funciona com escopos de funções envolventes. Você não pode usá-la para:

  1. Escopo global (use global em vez disso):
python
x = "global"
 
def func():
    nonlocal x  # SyntaxError: no binding for nonlocal 'x' found
    x = "modified"
  1. Variáveis que não existem em nenhum escopo envolvente:
python
def outer():
    def inner():
        nonlocal count  # SyntaxError: no binding for nonlocal 'count' found

Principais Lições Sobre nonlocal

  • Use nonlocal para modificar variáveis de escopos de funções envolventes
  • nonlocal procura em escopos de funções envolventes, não no escopo global
  • Ler variáveis envolventes não exige nonlocal — apenas modificá-las exige
  • nonlocal possibilita padrões poderosos para criar funções com estado privado
  • Vamos aprender mais sobre closures e funções fábrica no Capítulo 23

A palavra-chave nonlocal é particularmente útil para criar funções que mantêm estado privado, como vimos nos exemplos de contador, conta e rastreador de eventos.

21.5) Apagando Nomes (Não Objetos) com del e o Que Isso Significa

Às vezes, você precisa remover uma variável do namespace (espaço de nomes) do seu programa — talvez para liberar memória em programas de longa duração, limpar variáveis temporárias ou remover entradas de coleções. A instrução del do Python lida com essas tarefas, mas é importante entender exatamente o que ela faz e o que ela não faz.

A instrução del em Python é frequentemente mal compreendida. Ela não apaga objetos — ela apaga nomes (vínculos de variáveis). Entender essa distinção é crucial para entender como o Python gerencia memória e referências.

O Que del Realmente Faz

A instrução del remove um nome do escopo atual:

python
x = 42
print(x)  # Output: 42
 
del x
 
# print(x)  # NameError: name 'x' is not defined

Depois de del x, o nome x não existe mais no escopo atual. Se você tentar usá-lo, o Python levanta um NameError porque o nome não está mais definido.

Apagar Nomes vs Apagar Objetos

Este é o insight principal: del remove o nome, não necessariamente o objeto ao qual o nome se refere:

python
# Criar uma lista e dois nomes que se referem a ela
original = [1, 2, 3]
reference = original  # Ambos os nomes se referem à mesma lista
 
print(original)   # Output: [1, 2, 3]
print(reference)  # Output: [1, 2, 3]
 
# Apagar um nome
del original
 
# A lista ainda existe porque 'reference' ainda se refere a ela
print(reference)  # Output: [1, 2, 3]
 
# print(original)  # NameError: name 'original' is not defined

A lista [1, 2, 3] continua existindo porque reference ainda se refere a ela. Apagar original só removeu aquele nome — não apagou o objeto lista em si.

Quando Objetos São Realmente Apagados

O Python apaga objetos automaticamente quando eles não são mais referenciados por nenhum nome. Isso se chama coleta de lixo (garbage collection):

python
data = [1, 2, 3]  # A lista é criada, 'data' se refere a ela
 
del data  # O nome 'data' é apagado
 
# Agora a lista não tem referências, então o Python eventualmente vai apagá-la
# (Isso acontece automaticamente - você não precisa fazer nada)

Quando apagamos data, a lista [1, 2, 3] não tem referências restantes, então o coletor de lixo do Python eventualmente vai recuperar a memória. Mas isso acontece automaticamente — você não controla quando.

Apagando Itens de Coleções

A instrução del também pode remover itens de coleções, mas isso é fundamentalmente diferente de apagar nomes. Quando você usa del com indexação ou slicing de coleções, você está modificando a coleção em si, não apagando um nome.

Esta é uma distinção importante: quando você escreve del numbers[2], você está chamando um método especial no objeto lista para remover um elemento. O nome numbers ainda existe e ainda se refere ao mesmo objeto lista — a lista só tem menos elementos agora.

python
# Apagando elementos de lista por índice
numbers = [10, 20, 30, 40, 50]
del numbers[2]  # Remover o elemento no índice 2
print(numbers)  # Output: [10, 20, 40, 50]
 
# Apagando fatias de lista
numbers = [10, 20, 30, 40, 50]
del numbers[1:3]  # Remover elementos do índice 1 ao 3 (exclusivo)
print(numbers)  # Output: [10, 40, 50]
 
# Apagando entradas de dicionário
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person)  # Output: {'name': 'Alice', 'city': 'Boston'}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai