2 - Conceptos esenciales previos en Python

Contexto del lenguaje

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.

2.1 Variables, referencias y mutabilidad

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.

2.2 Listas y acceso por índice

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.

2.3 Clases simples para TDAs

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.

2.4 Colecciones especializadas

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:

Abrir editor online

2.5 ¿Cuándo conviene usar cada representación?

La representación depende de los requisitos de la aplicación:

  • Listas (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.
  • Listas enlazadas propias: útiles si se desea control total sobre nodos o si se estudia la estructura a bajo nivel; implican más código y validaciones.
  • 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.