Antes de implementar una pila en Python conviene alinear nociones de tipos, mutabilidad y colecciones nativas. Cada decisión sobre el diseño (lista acotada, deque, lista enlazada) se fundamenta en cómo almacenamos referencias y cómo manejamos el crecimiento.
En este tema recordamos conceptos esenciales para que, en los siguientes, podamos enfocarnos en la lógica de la pila sin distraernos con detalles del lenguaje.
En Python las variables almacenan referencias a objetos. No distinguimos reservas estáticas o dinámicas: el recolector de basura libera lo que deja de usarse. Importa saber si un objeto es mutable (listas, diccionarios) o inmutable (tuplas, enteros, str), porque las pilas suelen apoyarse en colecciones mutables.
numeros = [1, 2, 3]
alias = numeros # Ambas variables apuntan a la misma lista
alias.append(4)
print(numeros) # [1, 2, 3, 4] (mutación compartida)
copia = numeros.copy() # Nueva lista independiente
copia.pop()
print(numeros) # [1, 2, 3, 4]
print(copia) # [1, 2, 3]
Para una pila, elegir colecciones mutables (listas o deque) evita recrear objetos en cada operación y mantiene las complejidades esperadas.
Las listas son secuencias ordenadas y mutables. Python valida los índices y lanza IndexError si se exceden los límites, lo que simplifica el control de errores.
pila = [] # lista vacia
pila.append(10) # push
pila.append(20)
pila.append(30)
tope = pila[-1] # peek
print(tope) # 30
valor = pila.pop() # pop
print(valor) # 30
print(pila) # [10, 20]
Las listas ofrecen append y pop en O(1) amortizado; para pilas pequeñas o medianas suelen ser suficientes sin dependencias adicionales.
Las clases permiten agrupar datos y comportamiento. No necesitamos definir estructuras ni administrar memoria manualmente: basta con encapsular la lista interna y exponer métodos.
class Pila:
def __init__(self, capacidad=None):
self._datos = []
self._capacidad = capacidad
def push(self, valor):
if self._capacidad and len(self._datos) >= self._capacidad:
raise OverflowError("Pila llena")
self._datos.append(valor)
def pop(self):
if not self._datos:
raise IndexError("Pila vacia")
return self._datos.pop()
def peek(self):
if not self._datos:
raise IndexError("Pila vacia")
return self._datos[-1]
Encapsular la estructura permite cambiar la representación interna sin modificar el código consumidor del TDA.
Python ofrece alternativas listas para usar que simplifican las pilas:
collections.deque: doble extremo con append/pop en O(1) consistente y sin realocaciones costosas.queue.LifoQueue: orientada a concurrencia, protege con locks y permite tamaños máximos.from collections import deque
from queue import LifoQueue
pila = deque()
pila.append('A')
pila.append('B')
pila.append('C')
print(pila.pop()) # C
print(pila.pop()) # B
seguro = LifoQueue(maxsize=2)
seguro.put(1)
seguro.put(2)
# seguro.put(3, block=False) # lanzaria queue.Full
print(seguro.get()) # 2
Estos tipos eliminan la necesidad de administrar realocaciones manuales y reducen la posibilidad de errores por desbordes o condiciones de carrera.
Prueba el ejemplo con deque y LifoQueue directamente en el editor online:
La representación depende de los requisitos de la aplicación:
list): sencillas, O(1) amortizado en append/pop al final, ideales para tamaños pequeños o medianos.collections.deque: rendimiento consistente aun con muchas operaciones y sin copias internas grandes; recomendada para cargas intensivas.queue.LifoQueue: preferible cuando se necesita sincronización entre hilos y un límite de capacidad.En resumen: usa listas cuando prima la simplicidad, deque para escenarios de alto tráfico, estructuras enlazadas para fines académicos o requisitos especiales, y LifoQueue si hay concurrencia.