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.
# 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: 0Neste 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.
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 definedAs 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:
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.0As 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:
# 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:
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 UnboundLocalErrorEsse 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:
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 UnboundLocalErrorA 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:
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) # NameErrorO 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:
# 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.80Neste exemplo:
tax_rateefree_shipping_thresholdsão valores globais de configuraçãosubtotal,tax,shippingetotalsão locais a cada chamada decalculate_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:
- Local (L): o escopo da função atual
- Enclosing (E): o escopo de quaisquer funções envolventes (funções que contêm a função atual)
- Global (G): o escopo no nível do módulo
- 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:
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.0Quando 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:
# 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.0Quando 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:
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:
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:
- Escopo local de
inner_function()— não encontrado - 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:
# 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: 18Quando o Python avalia z + y + multiplier dentro de inner():
- L (Local): encontra
z = 3 - E (Enclosing): encontra
y = 5emouter() - G (Global): encontra
multiplier = 10 - 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:
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
globalCada print() vê um value diferente porque o Python para na primeira correspondência:
- Dentro de
inner(): encontravaluelocalmente → imprime "local" - Dentro de
outer()mas fora deinner(): encontravalueno escopo deouter()→ imprime "enclosing" - No nível do módulo: encontra
valueglobalmente → imprime "global"
Visualizando a Ordem de Busca LEGB
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:
- Prever valores de variáveis: você sabe exatamente qual variável o Python vai usar
- Evitar conflitos de nomes: você entende quando nomes fazem shadowing entre si
- Projetar funções melhores: você consegue decidir qual escopo é apropriado para cada variável
- 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:
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 UnboundLocalErrorIsso 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."
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: 2A 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:
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: 2Tanto 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:
# 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: 2Este 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:
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:
def add_to_total(current_total, value):
return current_total + value
total = 0
total = add_to_total(total, 10) # Claro: total está sendo atualizadoA 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:
# 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 == 20Problema 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:
# 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:
# 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: 900Use valores de retorno:
# 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: 900A segunda versão é mais flexível, testável e reutilizável.
Quando global É Realmente Apropriado
Existem usos legítimos para global:
- Configuração que realmente precisa ser global:
# 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"- Contadores para debugging ou estatísticas:
# 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çãoPrincipais Lições Sobre global
- Use
globalapenas 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:
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 UnboundLocalErrorO 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."
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:
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: 2Cada 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:
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()globalsempre se refere ao escopo no nível do módulononlocalse 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:
- Escopo global (use
globalem vez disso):
x = "global"
def func():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = "modified"- Variáveis que não existem em nenhum escopo envolvente:
def outer():
def inner():
nonlocal count # SyntaxError: no binding for nonlocal 'count' foundPrincipais Lições Sobre nonlocal
- Use
nonlocalpara modificar variáveis de escopos de funções envolventes nonlocalprocura em escopos de funções envolventes, não no escopo global- Ler variáveis envolventes não exige
nonlocal— apenas modificá-las exige nonlocalpossibilita 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:
x = 42
print(x) # Output: 42
del x
# print(x) # NameError: name 'x' is not definedDepois 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:
# 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 definedA 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):
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.
# 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'}