Listas en Python: la estructura de datos más versátil

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 20 min de lectura
Τὸ ὅλον πλεῖόν ἐστι τοῦ μέρους
«El todo es más que la suma de sus partes»
Aristóteles · Filósofo y lógico · 384 a.C. – 322 a.C.
Aristóteles fue el primero en sistematizar el conocimiento en categorías ordenadas: animales, plantas, minerales… cada cosa en su lugar dentro de una clasificación que podía recorrerse de principio a fin. Esto es, exactamente, una lista de Python. Una lista no es solo un montón de datos: es una secuencia con posición, con orden, con la capacidad de crecer y cambiar. Aristóteles habría apreciado lista.sort(): ordenar no destruye la colección, la revela. Y habría anotado en su Organon: usa append() para añadir al final, insert(0, x) para añadir al principio — y nunca modifiques una lista mientras la iteras.

La lista es la estructura de datos más usada en Python. Es una secuencia ordenada y mutable que puede contener elementos de cualquier tipo, incluso mezclados y repetidos. A diferencia de los arrays en C o Java, la lista de Python no requiere declarar el tipo ni el tamaño de antemano: crece y encoge según necesites.

📋 Crear listas y acceder por índice

La sintaxis más directa para crear una lista es con corchetes [], separando los elementos con comas. Python índica las posiciones empezando desde 0 para el primer elemento, y admite índices negativos que cuentan desde el final.

# Crear listas
vacia     = []
numeros   = [1, 2, 3, 4, 5]
frutas    = ["manzana", "pera", "naranja", "uva"]
mixta     = [42, "hola", True, 3.14, None]       # tipos mezclados
anidada   = [[1, 2], [3, 4], [5, 6]]              # lista de listas

# Otras formas de crear listas
desde_range = list(range(1, 6))       # [1, 2, 3, 4, 5]
desde_str   = list("Python")          # ['P','y','t','h','o','n']
repetida    = [0] * 5                 # [0, 0, 0, 0, 0]
repetida2   = ["ok"] * 3             # ['ok', 'ok', 'ok']

# Acceso por índice positivo (empieza en 0)
frutas[0]    # "manzana"   ← primer elemento
frutas[1]    # "pera"
frutas[3]    # "uva"       ← último (índice = longitud - 1)

# Acceso por índice negativo (desde el final)
frutas[-1]   # "uva"       ← último
frutas[-2]   # "naranja"   ← penúltimo
frutas[-4]   # "manzana"   ← equivale a frutas[0]

# Longitud de la lista
len(frutas)  # 4

# Comprobar si un elemento existe
"pera" in frutas      # True
"kiwi" in frutas      # False
"kiwi" not in frutas  # True

# IndexError si el índice no existe
frutas[10]   # IndexError: list index out of range
Estantería de madera oscura con libros de colores ordenados y numerados en filas
Una estantería: elementos ordenados y accesibles por posición, que puedes añadir o retirar libremente. La metáfora perfecta de una lista de Python. Fuente: Pexels (licencia libre).

✂️ Slicing: extraer sublistas con elegancia

El slicing (rebanado) permite extraer partes de una lista con la sintaxis [inicio:fin:paso]. El índice fin es siempre excluido. Cualquiera de los tres valores puede omitirse, tomando sus valores por defecto: inicio = 0, fin = len(lista), paso = 1.

L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Sintaxis básica: [inicio:fin]  (fin excluido)
L[2:5]      # [2, 3, 4]           — índices 2, 3, 4
L[:3]       # [0, 1, 2]           — desde el principio hasta el 2
L[7:]       # [7, 8, 9]           — desde el 7 hasta el final
L[:]        # [0,1,2,...,9]       — copia completa (útil para copiar)

# Con índices negativos
L[-3:]      # [7, 8, 9]           — los últimos 3
L[:-3]      # [0,1,2,3,4,5,6]    — todo excepto los últimos 3

# Con paso
L[::2]      # [0, 2, 4, 6, 8]    — cada 2 elementos (índices pares)
L[1::2]     # [1, 3, 5, 7, 9]    — índices impares
L[::-1]     # [9, 8, 7, ..., 0]  — invertir la lista
L[1:8:3]    # [1, 4, 7]           — inicio=1, fin=8, paso=3

# Slicing en strings (misma sintaxis)
"Python"[::-1]       # "nohtyP"
"abcdefg"[2:5]       # "cde"

# Modificar una porción con slicing
L = [0, 1, 2, 3, 4, 5]
L[1:4] = [10, 20]    # reemplaza índices 1,2,3 → [0, 10, 20, 4, 5]
L[2:2] = [99, 98]    # inserta sin eliminar → [0, 10, 99, 98, 20, 4, 5]
L[1:3] = []          # elimina esa porción

# Copia superficial con slicing
original = [1, 2, 3]
copia    = original[:]    # nuevo objeto, mismos valores
copia.append(4)
print(original)           # [1, 2, 3]  ← no afectado
💡 Índices negativos: Python indexa desde el final con negativos. -1 es el último elemento, -2 el penúltimo. L[-3:] significa "los últimos tres", sea cual sea la longitud de la lista.

🔧 Métodos esenciales: append, insert, remove, pop…

Las listas en Python son objetos y tienen métodos propios para modificarlas. Todos los métodos que modifican la lista operan in-place (modifican el objeto original) y devuelven None, salvo pop() e index()/count() que devuelven un valor.

L = [3, 1, 4, 1, 5, 9, 2, 6]

# --- AÑADIR ELEMENTOS ---
L.append(7)          # añade 7 al final:        [..., 6, 7]
L.insert(0, 0)       # inserta 0 en índice 0:   [0, 3, 1, ...]
L.extend([8, 5, 3])  # añade varios al final (equivale a +=)
L += [10, 11]        # también válido (extend interno)

# --- ELIMINAR ELEMENTOS ---
L.remove(1)          # elimina la primera aparición de 1
ultimo  = L.pop()    # elimina y devuelve el último
tercero = L.pop(2)   # elimina y devuelve el elemento en índice 2
del L[0]             # elimina el elemento en índice 0
del L[1:3]           # elimina porción (slicing)
L.clear()            # vacía la lista → []

# --- BUSCAR ---
L = [10, 20, 30, 20, 40]
L.index(20)          # 1  ← índice de la primera aparición
L.index(20, 2)       # 3  ← busca a partir del índice 2
L.count(20)          # 2  ← cuántas veces aparece

# --- REORDENAR ---
L.reverse()          # invierte in-place
L.sort()             # ordena ascendente in-place
L.sort(reverse=True) # ordena descendente in-place

# --- COPIAR ---
copia = L.copy()     # copia superficial (equivale a L[:])

# --- FUNCIONES BUILT-IN (no modifican la lista) ---
len(L)               # longitud
sum(L)               # suma (si son números)
min(L), max(L)       # mínimo y máximo
sorted(L)            # nueva lista ordenada (L no cambia)
list(reversed(L))    # nueva lista invertida (L no cambia)
enumerate(L)         # genera (índice, valor) para iterar
zip(L1, L2)          # empareja elementos de dos listas
Infografía de operaciones sobre listas en Python: añadir, eliminar, buscar, ordenar y copiar con sintaxis y coste O(n)
Mapa completo de operaciones sobre listas: métodos de modificación, búsqueda, ordenación y copia. Incluye el coste computacional (Big O) de cada operación para elegir la más eficiente. Infografía: Ciberaula.

🔢 Ordenar listas: sort() vs sorted()

Python ofrece dos formas de ordenar: sort() modifica la lista original, sorted() crea una lista nueva. Ambos aceptan el parámetro key para especificar el criterio de ordenación, y reverse=True para orden descendente.

numeros = [3, 1, 4, 1, 5, 9, 2, 6]

# sort() — modifica in-place, devuelve None
numeros.sort()                   # [1, 1, 2, 3, 4, 5, 6, 9]
numeros.sort(reverse=True)       # [9, 6, 5, 4, 3, 2, 1, 1]

# sorted() — devuelve lista nueva, no modifica el original
ordenada = sorted(numeros)       # nueva lista
print(numeros)                   # sin cambios

# ⚠️ Error clásico: guardar el resultado de sort()
resultado = numeros.sort()       # resultado = None ← trampa
resultado = sorted(numeros)      # ✅ esto sí funciona

# Ordenar con criterio personalizado (key=)
palabras = ["banana", "manzana", "kiwi", "pera", "fresa"]

# Por longitud
palabras.sort(key=len)
# ['kiwi', 'pera', 'fresa', 'banana', 'manzana']

# Por último carácter
palabras.sort(key=lambda p: p[-1])

# Por segunda letra
palabras.sort(key=lambda p: p[1])

# Ordenar lista de dicts por un campo
personas = [
    {"nombre": "Ana",   "edad": 28},
    {"nombre": "Luis",  "edad": 22},
    {"nombre": "Marta", "edad": 35},
]
personas.sort(key=lambda p: p["edad"])
# [Luis 22, Ana 28, Marta 35]

# Ordenar por múltiples criterios (primero edad, luego nombre)
personas.sort(key=lambda p: (p["edad"], p["nombre"]))

# sorted() funciona con cualquier iterable
sorted("Python")         # ['P', 'h', 'n', 'o', 't', 'y'] (alfabético)
sorted((5, 3, 1, 4, 2)) # [1, 2, 3, 4, 5] (desde tupla)

📋 Copiar listas: la trampa de la referencia

Este es uno de los errores más frecuentes en Python para principiantes. La asignación b = a no copia la lista: hace que b y a apunten al mismo objeto en memoria. Cualquier cambio en uno afecta al otro.

# ❌ Asignación simple — NO es una copia
a = [1, 2, 3]
b = a               # b es el mismo objeto que a
b.append(4)
print(a)            # [1, 2, 3, 4]  ← a también cambió
print(b is a)       # True

# ✅ Copia superficial (suficiente para listas de valores simples)
a = [1, 2, 3]
b = a[:]            # slicing completo
b = list(a)         # constructor list()
b = a.copy()        # método copy()
# Los tres son equivalentes para listas planas

b.append(4)
print(a)            # [1, 2, 3]  ← a no cambia ✅
print(b is a)       # False

# ⚠️ Copia superficial NO es suficiente para listas anidadas
a = [[1, 2], [3, 4]]
b = a[:]            # copia superficial
b[0].append(99)     # modifica la sublista
print(a)            # [[1, 2, 99], [3, 4]]  ← a también cambió

# ✅ Copia profunda para listas anidadas
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0].append(99)
print(a)            # [[1, 2], [3, 4]]  ← intacta
⚠️ Regla: Usa a[:] o list(a) para copias simples. Si la lista contiene otras listas, diccionarios u objetos mutables, usa copy.deepcopy(a).

🏗️ Listas anidadas y matrices

Una lista puede contener otras listas, formando estructuras bidimensionales o de mayor profundidad. Son la forma más sencilla de representar matrices y tablas en Python antes de usar NumPy.

# Matriz 3×3
matriz = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Acceso: primero fila, luego columna
matriz[0][0]   # 1  ← fila 0, columna 0
matriz[1][2]   # 6  ← fila 1, columna 2
matriz[2][-1]  # 9  ← última columna de la última fila

# Iterar por filas
for fila in matriz:
    print(fila)

# Iterar por todos los elementos
for fila in matriz:
    for elemento in fila:
        print(elemento, end=" ")
# 1 2 3 4 5 6 7 8 9

# Comprensión para aplanar la matriz en una lista
plana = [x for fila in matriz for x in fila]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Transponer una matriz (filas → columnas)
transpuesta = [[fila[i] for fila in matriz] for i in range(len(matriz[0]))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

# Más elegante con zip:
transpuesta = [list(fila) for fila in zip(*matriz)]
# zip(*matriz) desempaqueta las filas y zip las recombina como columnas

# Crear una matriz NxM de ceros
N, M = 3, 4
# ✅ Forma correcta (cada fila es un objeto diferente)
ceros = [[0] * M for _ in range(N)]

# ❌ Forma incorrecta (todas las filas son el MISMO objeto)
ceros_mal = [[0] * M] * N
ceros_mal[0][0] = 1
print(ceros_mal)  # [[1,0,0,0],[1,0,0,0],[1,0,0,0]] ← todas cambian
Cajas de cartón apiladas en filas y columnas formando una cuadrícula ordenada en un almacén
Una cuadrícula de cajas: filas y columnas, cada celda accesible por su posición. La representación física más clara de una matriz Python: una lista de listas. Fuente: Pexels (licencia libre).

💡 Patrones idiomáticos con listas

# 1. Comprobar si la lista está vacía
lista = []
if not lista:            # Pythonic ✅ (lista vacía es falsy)
    print("vacía")
if len(lista) == 0:      # también válido pero más verboso

# 2. Obtener el primer / último elemento con seguridad
def primero(L, defecto=None):
    return L[0] if L else defecto

def ultimo(L, defecto=None):
    return L[-1] if L else defecto

# 3. Eliminar duplicados manteniendo el orden (Python 3.7+)
lista = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
sin_dups = list(dict.fromkeys(lista))   # [3, 1, 4, 5, 9, 2, 6]

# 4. Aplanar una lista de listas
from itertools import chain
anidada  = [[1, 2], [3, 4], [5]]
plana    = list(chain.from_iterable(anidada))   # [1, 2, 3, 4, 5]

# 5. Separar pares e impares (o cualquier partición)
numeros = range(10)
pares   = [x for x in numeros if x % 2 == 0]
impares = [x for x in numeros if x % 2 != 0]

# 6. Zip: iterar dos listas en paralelo
nombres = ["Ana", "Luis", "Marta"]
notas   = [9.2, 7.5, 8.8]
for nombre, nota in zip(nombres, notas):
    print(f"{nombre}: {nota}")

# 7. Enumerate: índice + valor al mismo tiempo
for i, fruta in enumerate(["manzana", "pera", "uva"]):
    print(f"{i}: {fruta}")
# 0: manzana  1: pera  2: uva

# 8. Girar / rotar una lista
lista = [1, 2, 3, 4, 5]
n = 2
rotada = lista[n:] + lista[:n]   # [3, 4, 5, 1, 2]

# 9. Intercalar dos listas
a = [1, 3, 5]
b = [2, 4, 6]
intercalada = [x for par in zip(a, b) for x in par]
# [1, 2, 3, 4, 5, 6]

# 10. Chunks: dividir en sublistas de tamaño n
def chunks(lst, n):
    return [lst[i:i+n] for i in range(0, len(lst), n)]
print(chunks([1,2,3,4,5,6,7], 3))  # [[1,2,3],[4,5,6],[7]]
Ficha de referencia completa de los métodos de lista en Python: sintaxis, descripción y valor de retorno
Ficha de referencia de los métodos de lista. Incluye todos los métodos del objeto list, su sintaxis completa, descripción y si devuelven None o un valor. Infografía: Ciberaula.

🛠️ Programa completo: gestor de tareas

"""
Gestor de tareas en línea de comandos.
Demuestra operaciones reales con listas de Python.
"""

tareas = []   # lista principal: cada tarea es un dict

def agregar(texto, prioridad=2):
    """Añade una tarea. Prioridad: 1=alta, 2=normal, 3=baja."""
    tareas.append({"texto": texto, "prioridad": prioridad, "hecho": False})

def completar(indice):
    """Marca una tarea como completada."""
    if 0 <= indice < len(tareas):
        tareas[indice]["hecho"] = True
    else:
        print(f"  Índice {indice} no válido (hay {len(tareas)} tareas).")

def eliminar_completadas():
    """Elimina todas las tareas completadas (modifica la lista en su lugar)."""
    tareas[:] = [t for t in tareas if not t["hecho"]]

def listar(mostrar_hechas=True):
    """Lista las tareas ordenadas por prioridad."""
    pendientes  = [t for t in tareas if not t["hecho"]]
    completadas = [t for t in tareas if t["hecho"]]
    simbolo_p   = {1: "🔴", 2: "🟡", 3: "🟢"}

    print(f"\n  📋 TAREAS PENDIENTES ({len(pendientes)})")
    for i, t in enumerate(sorted(pendientes, key=lambda x: x["prioridad"])):
        idx = tareas.index(t)
        print(f"    [{idx}] {simbolo_p[t['prioridad']]} {t['texto']}")

    if mostrar_hechas and completadas:
        print(f"\n  ✅ COMPLETADAS ({len(completadas)})")
        for t in completadas:
            print(f"    ✓  {t['texto']}")

def buscar(termino):
    """Devuelve tareas que contienen el término (case-insensitive)."""
    termino = termino.lower()
    return [t for t in tareas if termino in t["texto"].lower()]

# Cargar datos de ejemplo
agregar("Revisar correo del cliente", prioridad=1)
agregar("Preparar presentación", prioridad=1)
agregar("Actualizar dependencias del proyecto", prioridad=2)
agregar("Comprar café para la oficina", prioridad=3)
agregar("Responder mensajes pendientes", prioridad=2)

# Menú principal
MENU = """
=== GESTOR DE TAREAS ===
1. Listar todas
2. Añadir tarea
3. Completar tarea
4. Buscar
5. Eliminar completadas
6. Salir
"""

while True:
    print(MENU)
    opcion = input("Opción: ").strip()

    if opcion == "1":
        listar()

    elif opcion == "2":
        texto = input("  Texto: ").strip()
        p     = input("  Prioridad (1=alta, 2=normal, 3=baja) [2]: ").strip() or "2"
        agregar(texto, int(p))
        print("  Tarea añadida.")

    elif opcion == "3":
        listar(mostrar_hechas=False)
        try:
            idx = int(input("  Índice a completar: ").strip())
            completar(idx)
        except ValueError:
            print("  Índice no válido.")

    elif opcion == "4":
        termino   = input("  Buscar: ").strip()
        resultado = buscar(termino)
        if resultado:
            for t in resultado:
                estado = "✓" if t["hecho"] else "·"
                print(f"  [{estado}] {t['texto']}")
        else:
            print("  Sin resultados.")

    elif opcion == "5":
        antes = len(tareas)
        eliminar_completadas()
        print(f"  Eliminadas {antes - len(tareas)} tarea(s) completada(s).")

    elif opcion == "6":
        print("Hasta luego.")
        break

🐛 Errores clásicos con listas

1. Modificar una lista mientras la iteras

# ❌ Comportamiento indefinido — omite elementos
lista = [1, -2, 3, -4, 5]
for x in lista:
    if x < 0:
        lista.remove(x)   # modifica la lista durante la iteración
print(lista)   # [1, 3, -4, 5]  ← ¡-4 no se elimina!

# ✅ Itera sobre una copia o usa comprensión
lista = [x for x in lista if x >= 0]   # [1, 3, 5]

2. Guardar el resultado de list.sort()

ordenada = lista.sort()    # sort() devuelve None
print(ordenada)            # None  ← trampa

ordenada = sorted(lista)   # sorted() devuelve lista nueva ✅

3. La trampa de [[0]*N]*M

matriz = [[0] * 3] * 3      # 3 referencias al MISMO objeto
matriz[0][0] = 1
print(matriz)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]]  ← todas cambian

# ✅ Usar comprensión para crear filas independientes
matriz = [[0] * 3 for _ in range(3)]
matriz[0][0] = 1
print(matriz)
# [[1, 0, 0], [0, 0, 0], [0, 0, 0]]  ✅

4. Confundir append() y extend()

a = [1, 2]
a.append([3, 4])    # añade una lista como UN elemento: [1, 2, [3, 4]]
a.extend([3, 4])    # añade cada elemento: [1, 2, 3, 4]

5. IndexError con lista vacía

lista = []
lista[0]    # IndexError: list index out of range

# ✅ Comprobar antes de acceder
if lista:
    primero = lista[0]

✅ Resumen y próximos pasos

Las listas son secuencias ordenadas y mutables, la estructura más versátil de Python. Domina el slicing para extraer partes con elegancia, los métodos para modificarlas de forma eficiente, y la distinción entre copias superficiales y profundas para evitar bugs difíciles de detectar.

La siguiente lección explora las tuplas: qué ocurre cuando la inmutabilidad no es una restricción sino una decisión de diseño.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Listas en Python: la estructura de datos más versátil

Las dudas más comunes respondidas de forma clara y directa.

Usa una lista cuando la colección necesita cambiar a lo largo del tiempo: añadir, eliminar o modificar elementos. Usa una tupla cuando los datos son fijos y no deben cambiar: coordenadas, colores RGB, valores de retorno múltiple, registros de base de datos. La tupla comunica intención al lector del código: "esto no cambia". Además, las tuplas son ligeramente más rápidas en acceso y pueden usarse como claves de diccionario porque son hashables. Regla práctica: empieza con tupla; si necesitas modificarla, cámbiala a lista.
list.sort() ordena la lista en su lugar (modifica el objeto original) y devuelve None. sorted() devuelve una nueva lista ordenada sin modificar la original. Úsalos así: lista.sort() cuando no necesitas la versión original y quieres ahorrar memoria. nueva = sorted(lista) cuando necesitas conservar ambas versiones o cuando el objeto no es una lista (sorted() acepta cualquier iterable: tuplas, generadores, strings). Un error muy común es escribir lista = lista.sort(), que asigna None a la variable porque sort() no devuelve nada.
La forma más idiomática en Python 3.7+ es usar dict.fromkeys(): sin_dups = list(dict.fromkeys(lista)). Esto funciona porque los diccionarios mantienen el orden de inserción y las claves son únicas. Otra opción es un bucle con un set auxiliar: visto = set(); resultado = [x for x in lista if not (x in visto or visto.add(x))]. No uses simplemente list(set(lista)) si el orden importa: set no garantiza ningún orden.
append(x) añade x como un único elemento al final de la lista, independientemente de su tipo. Si x es una lista, la añade como un elemento anidado: [1,2].append([3,4]) → [1, 2, [3,4]]. extend(iterable) añade cada elemento del iterable individualmente al final: [1,2].extend([3,4]) → [1, 2, 3, 4]. En términos de rendimiento, extend() es equivalente a la concatenación con +=, pero más eficiente que hacer append() en un bucle.
Porque la asignación en Python no copia el objeto, crea una nueva referencia al mismo objeto. b = a hace que b y a apunten a la misma lista en memoria. Para obtener una copia independiente, usa b = a[:] o b = list(a) (copia superficial, suficiente para listas de tipos simples). Si la lista contiene objetos mutables (listas anidadas, dicts), necesitas una copia profunda: import copy; b = copy.deepcopy(a).
Usa una comprensión cuando la operación es una transformación o filtrado simple sobre un iterable y el resultado es legible en una línea. Un bucle for es mejor cuando la lógica es compleja, tiene efectos secundarios (imprimir, escribir en disco, modificar variables externas), necesita manejo de excepciones, o la comprensión requeriría más de dos líneas para ser comprensible. La legibilidad siempre tiene prioridad sobre la concisión.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Listas en Python: la estructura de datos más versátil? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!