21. Alcance de variables y resolución de nombres
Cuando creas una variable en Python, ¿dónde “vive”? ¿Puede una función ver variables creadas fuera de ella? ¿Puede el código fuera de una función acceder a variables creadas dentro de ella? Estas preguntas tratan sobre el alcance(scope): la región de tu programa donde un nombre es visible y se puede usar.
Comprender el alcance es crucial para escribir funciones que funcionen correctamente y de forma predecible. Sin este conocimiento, podrías crear bugs accidentalmente en los que las variables no tienen los valores que esperas, o en los que los cambios en las variables no persisten como se pretende.
En este capítulo, exploraremos cómo Python determina a qué variable se refiere un nombre, cómo controlar dónde son accesibles las variables y qué ocurre cuando eliminas un nombre. Al final, comprenderás las reglas que gobiernan la visibilidad de variables en programas de Python.
21.1) Variables locales y globales
Cada variable en Python existe dentro de un alcance(scope) específico: una región de código donde ese nombre de variable está definido y es accesible. Los dos alcances más fundamentales son local y global.
Comprender el alcance global
Las variables creadas en el nivel superior de tu programa —fuera de cualquier función— existen en el alcance global. Estas se llaman variables globales, y son accesibles desde cualquier parte de tu módulo después de que se definan.
# Variable global: definida a nivel de módulo
total_users = 0
def show_user_count():
# Esta función puede LEER la variable global
print(f"Total users: {total_users}")
show_user_count() # Output: Total users: 0
print(total_users) # Output: 0En este ejemplo, total_users es una variable global. Tanto la función show_user_count() como el código a nivel de módulo pueden acceder a ella. Piensa en las variables globales como algo visible a lo largo de todo tu archivo de programa.
Comprender el alcance local
Las variables creadas dentro de una función existen en el alcance local de esa función. Estas se llaman variables locales, y solo son accesibles dentro de la función donde se definen. Una vez que la función termina de ejecutarse, las variables locales desaparecen.
def calculate_discount(price):
# discount_rate es LOCAL para esta función
discount_rate = 0.15
discount_amount = price * discount_rate
return discount_amount
result = calculate_discount(100)
print(result) # Output: 15.0
# Esto causaría un error: discount_rate no existe aquí
# print(discount_rate) # NameError: name 'discount_rate' is not definedLas variables discount_rate y discount_amount existen solo mientras calculate_discount() se está ejecutando. Después de que la función retorna, estos nombres ya no existen. Esto, de hecho, es algo bueno: evita que las funciones llenen tu programa con variables temporales.
Por qué importa el alcance local
El alcance local proporciona encapsulación(encapsulation): cada función tiene su propio espacio de trabajo privado. Esto significa que puedes usar los mismos nombres de variables en distintas funciones sin conflictos:
def calculate_tax(amount):
rate = 0.08 # Variable local
return amount * rate
def calculate_shipping(weight):
rate = 5.00 # Variable local diferente con el mismo nombre
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.0Ambas funciones usan una variable llamada rate, pero son variables completamente separadas en distintos alcances locales. Los cambios a rate en una función no afectan a rate en la otra función. Este aislamiento hace que las funciones sean más fiables y más fáciles de entender.
Leer variables globales desde funciones
Las funciones pueden leer variables globales sin ninguna sintaxis especial:
# Configuración global
max_login_attempts = 3
def check_login(password):
# Leyendo variable 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.La función check_login() puede leer max_login_attempts porque es una variable global. Sin embargo, hay una limitación importante que necesitamos entender.
La regla de que una asignación crea variables locales
Aquí es donde el alcance se pone complicado. Si asignas a un nombre de variable dentro de una función, Python crea una nueva variable local con ese nombre, incluso si existe una variable global con el mismo nombre:
counter = 0 # Variable global
def increment_counter():
# ADVERTENCIA: Esto crea una NUEVA variable local llamada counter; solo para demostración
# PROBLEMA: Intentar leer counter antes de asignarle un valor localmente
counter = counter + 1 # UnboundLocalError: local variable 'counter' referenced before assignment
print(counter)
# increment_counter() # Esta llamada produce UnboundLocalErrorEste código falla porque Python ve la asignación counter = counter + 1 y decide que counter debe ser una variable local. Pero luego, cuando intenta evaluar counter + 1, la variable local counter aún no tiene un valor: estamos intentando usarla antes de haberle asignado uno.
Esta es una fuente común de confusión. La regla es: si una función asigna a un nombre de variable en cualquier parte de su cuerpo, ese nombre se trata como local a lo largo de toda la función, incluso antes de la asignación.
Veámoslo más claramente:
message = "Hello" # Variable global
def show_message():
print(message) # Esto funciona: solo está leyendo la global
def change_message():
# ADVERTENCIA: Esto demuestra un error común; solo para demostración
# PROBLEMA: Python ve la asignación abajo, así que message se trata como local en toda la función
print(message) # UnboundLocalError!
message = "Goodbye" # Esto hace que message sea local para TODA la función
show_message() # Output: Hello
# change_message() # Esta llamada produce UnboundLocalErrorLa función show_message() funciona bien porque solo lee message. Pero change_message() falla porque la asignación en la segunda línea hace que Python trate message como local en toda la función, incluida la sentencia print() que aparece antes de la asignación.
Los parámetros son variables locales
Los parámetros de una función son variables locales que obtienen sus valores iniciales a partir de los argumentos pasados cuando se llama a la función:
def greet(name): # 'name' es una variable local
greeting = f"Hello, {name}!" # 'greeting' también es local
return greeting
message = greet("Alice")
print(message) # Output: Hello, Alice!
# Ni 'name' ni 'greeting' existen aquí
# print(name) # NameErrorEl parámetro name existe solo dentro de la función greet(). Se crea cuando se llama a la función y desaparece cuando la función retorna.
Ejemplo práctico: cálculo de un carrito de compras
Veamos cómo el alcance local y global trabajan juntos en un escenario realista:
# Configuración global
tax_rate = 0.08
free_shipping_threshold = 50
def calculate_total(subtotal):
# Variables locales para este cálculo
tax = subtotal * tax_rate # Leyendo tax_rate global
# Determinar el costo de envío
if subtotal >= free_shipping_threshold: # Leyendo el umbral global
shipping = 0
else:
shipping = 5.99
total = subtotal + tax + shipping
return total
# Calcular para diferentes valores del carrito
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.80En este ejemplo:
tax_rateyfree_shipping_thresholdson valores de configuración globalsubtotal,tax,shippingytotalson locales a cada llamada decalculate_total()- Cada llamada de la función obtiene su propio conjunto separado de variables locales
- La función puede leer la configuración global, pero no la modifica
Esta separación de responsabilidades hace que el código sea claro: las variables globales guardan la configuración que aplica en todas partes, mientras que las variables locales guardan resultados temporales de cálculo específicos para cada llamada a la función.
21.2) La regla LEGB para la resolución de nombres
Cuando Python se encuentra con un nombre de variable, ¿cómo sabe a qué variable te refieres? Python sigue un orden de búsqueda específico llamado la regla LEGB (LEGB rule). LEGB significa Local, Enclosing, Global, Built-in: los cuatro alcances que Python busca, en ese orden.
Los cuatro alcances en LEGB
Entendamos cada alcance en la jerarquía LEGB:
- Local (L): el alcance de la función actual
- Enclosing (E): el alcance de cualquier función envolvente (funciones que contienen a la función actual)
- Global (G): el alcance a nivel de módulo
- Built-in (B): los nombres incorporados de Python como
print,len,int, etc.
Cuando usas un nombre de variable, Python busca estos alcances en orden: L → E → G → B. Usa la primera coincidencia que encuentra y deja de buscar.
Alcance local: el primer lugar donde Python busca
Python siempre revisa primero el alcance local:
def calculate_price():
price = 100 # Variable local
tax = 0.08 # Variable local
total = price * (1 + tax)
return total
result = calculate_price()
print(result) # Output: 108.0Cuando Python ve price, tax y total dentro de calculate_price(), los encuentra en el alcance local y usa esos valores. La búsqueda se detiene en el alcance local: Python no necesita buscar más.
Alcance global: cuando lo local no lo tiene
Si un nombre no se encuentra localmente, Python revisa el alcance global:
# Variables globales
default_tax_rate = 0.08
default_currency = "USD"
def calculate_price(amount):
# 'amount' es local, se encuentra de inmediato
# 'default_tax_rate' no es local, se encuentra en el alcance global
total = amount * (1 + default_tax_rate)
return total
result = calculate_price(100)
print(result) # Output: 108.0Cuando Python encuentra default_tax_rate dentro de la función, no lo encuentra localmente, así que busca en el alcance global y lo encuentra allí.
Alcance built-in: nombres predefinidos de Python
Si un nombre no se encuentra en el alcance local ni global, Python revisa el alcance built-in: los nombres que Python proporciona automáticamente:
def process_data(numbers):
# 'numbers' es local
# 'len' no es local ni global: es built-in
count = len(numbers)
# 'max' también es built-in
maximum = max(numbers)
return count, maximum
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result) # Output: (5, 30)Los nombres len y max no están definidos en tu código: son funciones incorporadas que Python proporciona. Cuando Python no encuentra estos nombres localmente ni globalmente, revisa el alcance built-in y los encuentra allí.
Alcance envolvente: funciones anidadas
El alcance envolvente entra en juego cuando tienes funciones anidadas: funciones definidas dentro de otras funciones. Aquí es donde la “E” en LEGB se vuelve importante:
def outer_function():
outer_var = "I'm from outer" # En alcance envolvente para inner_function
def inner_function():
inner_var = "I'm from inner" # Local para inner_function
# inner_function puede ver tanto inner_var (local) como 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(), el alcance de outer_function() es un alcance envolvente. Cuando inner_function() referencia outer_var, Python busca:
- Alcance local de
inner_function()— no se encuentra - Alcance envolvente de
outer_function()— ¡se encuentra! Usa este valor
LEGB en acción: ejemplo simple
Veamos los cuatro alcances trabajando juntos en un ejemplo claro y directo:
# Built-in: len (Python proporciona esto)
# Global: multiplier
multiplier = 10
def outer(x):
# Alcance envolvente para inner
y = 5
def inner(z):
# Alcance local
# z es local (L)
# y es del alcance envolvente (E)
# multiplier es del alcance global (G)
# len es del alcance built-in (B)
result = len([z, y, multiplier]) # ¡Usa los cuatro alcances!
return z + y + multiplier
return inner(3)
answer = outer(100)
print(answer) # Output: 18Cuando Python evalúa z + y + multiplier dentro de inner():
- L (Local): encuentra
z = 3 - E (Enclosing): encuentra
y = 5enouter() - G (Global): encuentra
multiplier = 10 - B (Built-in): encuentra la función
len
Este ejemplo demuestra claramente cómo Python busca a través de los cuatro alcances para resolver nombres.
Sombreado: cuando los alcances internos ocultan nombres externos
Si el mismo nombre existe en múltiples alcances, el alcance más interno “gana”: esto se llama sombreado(shadowing):
value = "global"
def outer():
value = "enclosing"
def inner():
value = "local"
print(value) # Which value?
inner()
print(value) # Which value?
outer()
print(value) # Which value?Output:
local
enclosing
globalCada sentencia print() ve un value diferente porque Python se detiene en la primera coincidencia:
- Dentro de
inner(): encuentravaluelocalmente → imprime "local" - Dentro de
outer()pero fuera deinner(): encuentravalueen el alcance deouter()→ imprime "enclosing" - A nivel de módulo: encuentra
valueglobalmente → imprime "global"
Visualizar el orden de búsqueda LEGB
Este diagrama muestra el proceso de búsqueda de Python. Empieza en el alcance más interno y va hacia fuera. Si el nombre no se encuentra en ningún alcance, Python lanza un NameError.
Por qué LEGB importa al escribir funciones
Comprender LEGB te ayuda a:
- Predecir valores de variables: sabes exactamente qué variable usará Python
- Evitar conflictos de nombres: entiendes cuándo los nombres se sombrean entre sí
- Diseñar mejores funciones: puedes decidir qué alcance es apropiado para cada variable
- Depurar problemas de alcance: cuando las variables no tienen los valores esperados, puedes rastrear LEGB
La regla LEGB es fundamental para cómo Python resuelve nombres. Cada vez que usas una variable, Python está siguiendo esta regla detrás de escena.
21.3) Usar la palabra clave global con cuidado
Hemos visto que las funciones pueden leer variables globales, pero ¿qué pasa si necesitas modificar una variable global desde dentro de una función? Ahí es donde entra la palabra clave global, pero debe usarse con moderación y cuidado.
El problema: la asignación crea variables locales
Como aprendimos antes, asignar a una variable dentro de una función crea una variable local:
counter = 0 # Variable global
def increment():
# ADVERTENCIA: Esto crea una NUEVA variable local llamada counter; solo para demostración
# PROBLEMA: Intentar leer counter antes de asignarle un valor localmente
counter = counter + 1 # UnboundLocalError!
# increment() # Esta llamada produce UnboundLocalErrorEsto falla porque Python ve la asignación y trata counter como local a lo largo de toda la función. Pero estamos intentando leer counter antes de haberle asignado un valor localmente.
Este es uno de los errores más comunes al trabajar con variables globales. El mensaje de error UnboundLocalError: local variable 'counter' referenced before assignment te dice exactamente qué pasó: Python decidió que counter era local (por la asignación), pero intentaste usarla antes de darle un valor.
La solución: declarar variables como global
La palabra clave global le dice a Python: “No crees una nueva variable local con este nombre. Usa la variable global en su lugar.”
counter = 0 # Variable global
def increment():
global counter # Decirle a Python que use el counter global
counter = counter + 1 # Ahora esto modifica la variable 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: 2La declaración global counter debe ir antes de usar la variable. Le dice a Python que cualquier asignación a counter en esta función debe modificar la variable global, no crear una local.
Múltiples variables globales
Puedes declarar múltiples variables como global en una sola sentencia:
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 como total_customers se declaran globales, así que la función puede modificar ambas.
Cuándo usar global: estado compartido
La palabra clave global es apropiada cuando necesitas mantener estado compartido(shared state): datos a los que múltiples funciones necesitan acceder y modificar:
# Estado del juego
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():
# Solo leyendo globales: no hace falta la palabra clave global
if game_over:
return "Game Over"
else:
return f"Playing - Score: {player_score}, Lives: {player_lives}"
# Jugar el juego
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 ejemplo muestra un uso apropiado de global: múltiples funciones necesitan modificar el estado compartido del juego. Sin embargo, nota que check_game_status() no necesita global porque solo lee las variables.
Por qué global debe usarse con cuidado
Aunque global a veces es necesario, abusar de él puede hacer que el código sea más difícil de entender y mantener. Aquí tienes por qué:
Problema 1: dependencias ocultas
Cuando las funciones modifican variables globales, no es obvio desde la llamada a la función qué está cambiando:
total = 0
def add_to_total(value):
global total
total += value
# ¿Qué hace esta función? No puedes saberlo sin leer su código
add_to_total(10)Compara esto con una función que devuelve un valor:
def add_to_total(current_total, value):
return current_total + value
total = 0
total = add_to_total(total, 10) # Claro: total se está actualizandoLa segunda versión hace explícito que total se está modificando.
Problema 2: las pruebas se vuelven más difíciles
Las funciones que modifican estado global son más difíciles de probar porque necesitas preparar y restablecer variables globales:
# Difícil de probar: depende del estado global
score = 0
def add_score(points):
global score
score += points
# Cada prueba necesita restablecer score
# Test 1
score = 0
add_score(10)
assert score == 10
# Test 2: hay que restablecer score de nuevo
score = 0
add_score(20)
assert score == 20Problema 3: las funciones no son reutilizables
Las funciones que dependen de variables globales específicas no se pueden reutilizar fácilmente en otros programas:
# Esta función solo funciona si hay una variable global llamada 'inventory'
inventory = []
def add_item(item):
global inventory
inventory.append(item)Mejores alternativas a global
En muchos casos, puedes evitar global usando valores de retorno y parámetros:
En 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: 900Usa valores de retorno:
# Usando valores de retorno (mejor)
def withdraw(balance, amount):
if amount <= balance:
return balance - amount, True
return balance, False
balance = 1000
balance, success = withdraw(balance, 100)
print(balance) # Output: 900La segunda versión es más flexible, más fácil de probar y más reutilizable.
Cuándo global es realmente apropiado
Hay usos legítimos de global:
- Configuración que realmente necesita ser global:
# Ajustes de toda la aplicación
debug_mode = False
log_level = "INFO"
def enable_debug():
global debug_mode, log_level
debug_mode = True
log_level = "DEBUG"- Contadores para depuración o estadísticas:
# Rastrear llamadas a la función para depuración
_function_call_count = 0
def tracked_function():
global _function_call_count
_function_call_count += 1
# ... resto de la funciónPuntos clave sobre global
- Usa
globalsolo cuando realmente necesitas modificar estado a nivel de módulo - Prefiere devolver valores y usar parámetros en su lugar
- Cuando uses
global, documenta por qué es necesario - Considera si tu diseño podría mejorarse para evitar
global - Recuerda: leer variables globales no requiere la palabra clave
global; solo modificarlas lo requiere
21.4) Usar nonlocal para modificar variables en funciones envolventes
Cuando tienes funciones anidadas, podrías necesitar modificar una variable del alcance de una función envolvente. La palabra clave nonlocal sirve para esto: es como global, pero para alcances de funciones envolventes en lugar del alcance global.
El problema: modificar variables envolventes
Así como la asignación crea variables locales por defecto, el mismo problema ocurre con los alcances envolventes:
def outer():
count = 0 # Variable en el alcance de outer
def inner():
# ADVERTENCIA: Esto crea una NUEVA variable local llamada count; solo para demostración
# PROBLEMA: Intentar leer count antes de asignarle un valor localmente
count = count + 1 # UnboundLocalError!
print(count)
inner()
# outer() # Esta llamada produce UnboundLocalErrorPython ve la asignación a count en inner() y la trata como una variable local. Pero estamos intentando leerla antes de asignarla localmente, causando un error.
La solución: la palabra clave nonlocal
La palabra clave nonlocal le dice a Python: “Esta variable no es local; búscala en el alcance de la función envolvente y usa esa.”
def outer():
count = 0 # Variable en el alcance de outer
def inner():
nonlocal count # Usar el count del alcance 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()Ahora inner() puede modificar la variable count del alcance de outer(). El cambio persiste después de que inner() retorna porque estamos modificando la variable real en el alcance envolvente.
Por qué nonlocal es útil: funciones que recuerdan estado
La palabra clave nonlocal habilita un patrón potente donde las funciones internas pueden mantener y modificar estado del alcance envolvente. Aprenderemos sobre closures y funciones fábrica en detalle en el Capítulo 23, pero por ahora, entiende que nonlocal permite que las funciones internas modifiquen variables de alcances envolventes.
Aquí tienes un ejemplo simple que muestra cómo funciona nonlocal:
def create_counter():
count = 0 # Esta variable está en el alcance envolvente para increment
def increment():
nonlocal count # Modificar count desde el alcance envolvente
count += 1
return count
return increment # Devolver la función interna
# Crear un contador
counter1 = create_counter()
print(counter1()) # Output: 1
print(counter1()) # Output: 2
print(counter1()) # Output: 3
# Crear otro contador independiente
counter2 = create_counter()
print(counter2()) # Output: 1
print(counter2()) # Output: 2Cada llamada a create_counter() crea una nueva variable count y una nueva función increment() que puede modificar ese count específico usando nonlocal.
nonlocal vs global
Es importante entender la diferencia:
x = "global"
def outer():
x = "enclosing"
def use_global():
global x # Se refiere al x global
print(f"use_global sees: {x}") # Output: use_global sees: global
def use_nonlocal():
nonlocal x # Se refiere al x de outer
print(f"use_nonlocal sees: {x}") # Output: use_nonlocal sees: enclosing
use_global()
use_nonlocal()
outer()globalsiempre se refiere al alcance a nivel de módulononlocalse refiere al alcance de la función envolvente más cercano
Cuándo no puedes usar nonlocal
La palabra clave nonlocal solo funciona con alcances de funciones envolventes. No puedes usarla para:
- Alcance global (usa
globalen su lugar):
x = "global"
def func():
nonlocal x # SyntaxError: no binding for nonlocal 'x' found
x = "modified"- Variables que no existen en ningún alcance envolvente:
def outer():
def inner():
nonlocal count # SyntaxError: no binding for nonlocal 'count' foundPuntos clave sobre nonlocal
- Usa
nonlocalpara modificar variables de alcances de funciones envolventes nonlocalbusca alcances de funciones envolventes, no el alcance global- Leer variables envolventes no requiere
nonlocal; solo modificarlas lo requiere nonlocalhabilita patrones potentes para crear funciones con estado privado- Aprenderemos más sobre closures y funciones fábrica en el Capítulo 23
La palabra clave nonlocal es particularmente útil para crear funciones que mantienen estado privado, como vimos con los ejemplos de contador, cuenta y rastreador de eventos.
21.5) Eliminar nombres (no objetos) con del y qué significa
A veces necesitas eliminar una variable del espacio de nombres de tu programa: quizá para liberar memoria en programas de larga ejecución, limpiar variables temporales o eliminar entradas de colecciones. La sentencia del de Python maneja estas tareas, pero es importante entender exactamente qué hace y qué no hace.
La sentencia del en Python a menudo se malinterpreta. No elimina objetos: elimina nombres (vinculaciones de variables). Comprender esta distinción es crucial para entender cómo Python gestiona la memoria y las referencias.
Qué hace realmente del
La sentencia del elimina un nombre del alcance actual:
x = 42
print(x) # Output: 42
del x
# print(x) # NameError: name 'x' is not definedDespués de del x, el nombre x ya no existe en el alcance actual. Si intentas usarlo, Python lanza un NameError porque el nombre ya no está definido.
Eliminar nombres vs eliminar objetos
Esta es la idea clave: del elimina el nombre, no necesariamente el objeto al que se refiere el nombre:
# Crear una lista y dos nombres que se refieren a ella
original = [1, 2, 3]
reference = original # Ambos nombres se refieren a la misma lista
print(original) # Output: [1, 2, 3]
print(reference) # Output: [1, 2, 3]
# Eliminar un nombre
del original
# La lista aún existe porque 'reference' todavía se refiere a ella
print(reference) # Output: [1, 2, 3]
# print(original) # NameError: name 'original' is not definedLa lista [1, 2, 3] sigue existiendo porque reference todavía se refiere a ella. Eliminar original solo eliminó ese nombre: no eliminó el objeto lista en sí.
Cuándo los objetos se eliminan de verdad
Python elimina automáticamente los objetos cuando ya no son referenciados por ningún nombre. A esto se le llama recolección de basura(garbage collection):
data = [1, 2, 3] # Se crea la lista, 'data' se refiere a ella
del data # Se elimina el nombre 'data'
# Ahora la lista no tiene referencias, así que Python eventualmente la eliminará
# (Esto ocurre automáticamente: no necesitas hacer nada)Cuando eliminamos data, la lista [1, 2, 3] no tiene referencias restantes, así que el recolector de basura de Python eventualmente recuperará la memoria. Pero esto ocurre automáticamente: tú no controlas cuándo.
Eliminar elementos de colecciones
La sentencia del también puede eliminar elementos de colecciones, pero esto es fundamentalmente diferente de eliminar nombres. Cuando usas del con indexación o slicing de una colección, estás modificando la colección en sí, no eliminando un nombre.
Esta es una distinción importante: cuando escribes del numbers[2], estás llamando a un método especial del objeto lista para eliminar un elemento. El nombre numbers sigue existiendo y sigue refiriéndose al mismo objeto lista: la lista simplemente tiene menos elementos ahora.
# Eliminar elementos de una lista por índice
numbers = [10, 20, 30, 40, 50]
del numbers[2] # Eliminar el elemento en el índice 2
print(numbers) # Output: [10, 20, 40, 50]
# Eliminar slices de una lista
numbers = [10, 20, 30, 40, 50]
del numbers[1:3] # Eliminar elementos desde el índice 1 hasta 3 (exclusivo)
print(numbers) # Output: [10, 40, 50]
# Eliminar entradas de un diccionario
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person) # Output: {'name': 'Alice', 'city': 'Boston'}