Skip to content
This page has been auto-translated and may contain errors.View in English

Tuplas y conjuntos

docs.scrimba.com

Ya conoces las listas. Python tiene dos tipos de colección más que resuelven problemas que las listas no pueden. Las tuplas mantienen un grupo fijo de valores que nunca cambiarán. Los conjuntos contienen solo valores únicos y te permiten verificar la pertenencia al instante sin importar cuán grande sea la colección.

El conjunto de herramientas de colecciones de Python tiene cuatro tipos. Las listas y los diccionarios manejan la mayoría de casos generales. Las tuplas y conjuntos resuelven los casos específicos: registros fijos donde la inmutabilidad es un activo, y colecciones de valores únicos donde la prueba de pertenencia O(1) es la prioridad.

Más allá de list y dict, Python te da tuple (una secuencia de longitud fija que no puedes cambiar después de crearla) y set (una colección desordenada de valores únicos, construida sobre una tabla hash, la misma estructura de búsqueda por contenido que hace rápida a dict). La división que impulsa toda decisión: una tupla es hashable (sus contenidos pueden reducirse a un único número para que pueda actuar como clave de diccionario o miembro de conjunto), una lista no lo es. Elige en función de lo que necesites que haga la colección, no en función de cuál es tu favorita.

Tuplas

Una tupla es un grupo ordenado de valores que no puede cambiar después de crearla. Los paréntesis definen una tupla, pero son opcionales. La coma es lo que realmente hace que sea una tupla. Una tupla de un solo elemento requiere una coma final.

Las tuplas son secuencias inmutables. La coma, no los paréntesis, es lo que crea una tupla. La inmutabilidad las hace hashables cuando todos sus elementos también lo son, lo que abre casos de uso que las listas no pueden llenar: claves de diccionario, miembros de conjunto y registros de estructura fija.

Una tuple es una secuencia de longitud fija que no puedes cambiar una vez que existe. Debido a que nada dentro se mueve, Python puede calcular un único hash para ella (un número derivado de su contenido) siempre que cada elemento sea hashable, lo que permite que una tupla sirva como clave de diccionario o miembro de conjunto donde una lista no puede. La indexación y segmentación funcionan exactamente como en una lista; no hay asignación de elementos, así que point[0] = 99 lanza TypeError cuando esa línea se ejecuta. La forma de un solo elemento (42,) necesita la coma final. Sin ella, los paréntesis solo agrupan y obtienes el valor desnudo 42.

python
point = (10, 20)
rgb = (255, 128, 0)
dimensions = (1920, 1080)
single = (42,)            # coma final requerida para una tupla de un elemento
also_tuple = 42, 99       # los paréntesis son opcionales; la coma hace que sea una tupla

El acceso por índice funciona exactamente como una lista. Intentar cambiar un elemento lanza un TypeError:

La indexación, segmentación e índices negativos funcionan idénticamente a las listas. Cualquier intento de asignar por índice lanza TypeError; esto es intencional, no una limitación.

Leer de una tupla, por índice, índice negativo o segmento, se comporta exactamente como una lista. Lo que difiere es escribir: no hay asignación de elementos, así que point[0] = 99 lanza TypeError en el momento en que esa línea se ejecuta. Ese es exactamente el punto de elegir una tupla en lugar de una lista. Es un valor en el que tú y cualquier lector posterior pueden confiar en que no cambiará debajo de ustedes.

python
point = (10, 20)
point[0]    # 10
point[1]    # 20
point[-1]   # 20

point[0] = 99    # TypeError: 'tuple' object does not support item assignment
JunoTuplas Una tupla es un grupo ordenado de valores que no puedes cambiar después de crearla. La coma es lo que la crea, no los paréntesis, por eso (42,) necesita esa solitaria coma final. Intenta reasignar un elemento y obtendrás un TypeError, y esa sensación de bloqueo es exactamente por qué elegirías una tupla.
JunoTuplas Las tuplas son secuencias inmutables, y la coma las hace, no los paréntesis. Esa inmutabilidad es una característica: hace que una tupla sea hashable cuando sus contenidos también lo son, para que pueda ser una clave de diccionario o miembro de conjunto donde una lista no puede. La asignación de elementos lanza TypeError por diseño.
JunoTuplas Leer una tupla coincide con una lista; escribir es lo que falta, así que point[0] = 99 lanza TypeError en tiempo de ejecución. Apóyate en eso: una tupla es el valor que pasas alrededor cuando necesitas una garantía de que nada descendente lo reescribirá. La trampa de un solo elemento (42,) atrapa a todos una vez.

Cuándo usar una tupla

Usa una tupla cuando tengas un pequeño grupo de valores relacionados que pertenezcan juntos y no cambiarán. Coordenadas (x, y), un color (r, g, b), un par nombre-puntuación ("Carlos", 87). La estructura fija señala a cualquiera que lea el código que este grupo se trata como una unidad única.

Las tuplas comunican estructura fija: un grupo de valores donde la posición tiene significado y el grupo se trata como una unidad. Su hashability las hace válidas como claves de diccionario, que las listas no pueden ser. El contrato que una tupla señala es: estos valores pertenecen juntos y no se supone que cambien.

Usa una tupla cuando tengas un registro de forma fija: un número conocido de campos donde cada posición significa algo específico. Debido a que una tupla es hashable (sus contenidos se reducen a un único número), encaja en cualquier lugar donde se requiera un valor hashable: una clave de diccionario, un miembro de conjunto, o los argumentos en caché detrás de functools.lru_cache, que recuerda los resultados de una función con clave en los argumentos con los que la llamaste. La señal semántica también difiere de una lista. Una tupla dice "estos campos pertenecen juntos y cada posición tiene un significado" (una coordenada, un color), donde una lista dice "una ejecución de cosas similares cuya longitud puede cambiar."

JunoCuándo usar una tupla Usa una tupla cuando tengas un pequeño grupo fijo donde cada punto significa algo: una coordenada, un color (r, g, b), un par nombre-y-puntuación. Debido a que las tuplas son hashables, incluso puedes usar una como clave de diccionario. Una lista no puede hacer eso, lo que sorprende a la gente la primera vez que lo intenta.
JunoCuándo usar una tupla Una tupla señala estructura fija: la posición tiene significado y el grupo viaja como una unidad. Esa es la diferencia de una lista, que dice "una secuencia cuya longitud puede variar." Y dado que las tuplas son hashables, funcionan como claves de diccionario donde las listas lanzan TypeError.
JunoCuándo usar una tupla Tupla para un registro de forma fija, lista para una ejecución de cosas similares de longitud variable. El beneficio es la hashability: una tupla se inserta en una clave de diccionario, un conjunto, o una clave de lru_cache, todos los lugares donde una lista no puede ir. Deja que el tipo lleve ese contrato en lugar de un comentario.
python
locations = {}
locations[(40, -74)] = "Nueva York"   # tupla como clave de diccionario, funciona
locations[[40, -74]] = "Nueva York"   # lista como clave de diccionario, TypeError

Desempaquetación

La desempaquetación extrae valores de una tupla y asigna cada uno a su propio nombre en una sola línea. El número de nombres debe coincidir con el número de valores. Usa * para capturar los elementos restantes en una lista.

La desempaquetación funciona en cualquier iterable: tuplas, listas, cadenas. El número de nombres de destino debe coincidir con la longitud del iterable, a menos que un destino con asterisco capture un segmento de longitud variable. La falta de coincidencia lanza ValueError. La desempaquetación es la forma idiomática de consumir múltiples valores de retorno de una función.

La desempaquetación funciona en cualquier cosa iterable (cualquier cosa sobre la que puedas iterar), no solo tuplas, recorriendo el lado derecho y vinculando cada valor a su nombre de destino en orden. Un destino con asterisco (*rest) absorbe los elementos restantes en una list, por lo que el número ya no tiene que coincidir exactamente. Una falta de coincidencia de conteo simple lanza ValueError en tiempo de ejecución. La forma que más usas es en un encabezado for: for name, score in pairs desempaqueta cada elemento a medida que itera, lo que se lee mucho más limpio que indexar en cada par a mano.

JunoDesempaquetación La desempaquetación extrae cada valor de una tupla o lista y le da su propio nombre en una línea, como x, y = point. El número de nombres tiene que coincidir con el número de valores, a menos que agregues *rest para recopilar lo que queda. Me hizo clic el día en que dejé de escribir point[0] y point[1] en todas partes.
JunoDesempaquetación La desempaquetación asigna cada valor a su propio nombre en una línea, y un destino con asterisco como *rest captura un segmento de longitud variable. Los conteos que no coinciden lanzan ValueError. Es la forma limpia de tomar múltiples valores de retorno de una función en lugar de indexar el resultado.
JunoDesempaquetación La desempaquetación se ejecuta en cualquier iterable y vincula de izquierda a derecha; *rest absorbe la holgura, una falta de coincidencia clara lanza ValueError. El lugar donde más se paga es el encabezado `for`, for name, score in pairs, que supera alcanzar cada par por índice.
python
point = (10, 20)
x, y = point

print(x)   # 10
print(y)   # 20

first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]

head, *middle, tail = [1, 2, 3, 4, 5]
# head = 1, middle = [2, 3, 4], tail = 5

Tuplas con nombre

Una tupla con nombre es una tupla donde cada posición tiene un nombre. En lugar de recordar que point[0] es la coordenada x, escribes point.x. Los valores siguen siendo inmutables; obtienes nombres de atributo legibles en lugar de posiciones numéricas.

namedtuple genera una clase que se comporta exactamente como una tupla pero agrega acceso a atributos con nombre. Es más ligera que una clase completa, inmutable y autodocumentada. Úsala cuando el acceso posicional de una tupla simple requeriría un comentario para ser entendido.

collections.namedtuple es una fábrica de clases (una función que construye y te devuelve una clase completamente nueva para usar). La clase que devuelve es una tuple con campos con nombre, por lo que cuesta la misma memoria que una tupla simple mientras se lee mucho mejor. Obtienes _asdict() (conviértela en un diccionario), _replace() (haz una copia con un campo cambiado, ya que no puedes mutar), y _fields de forma gratuita. Cuando necesites más, valores predeterminados o anotaciones de tipo, typing.NamedTuple cubre el caso anotado, y dataclasses.dataclass es la opción moderna una vez que quieras métodos o mutabilidad opcional.

JunoTuplas con nombre Una tupla con nombre le da a cada posición un nombre, así que escribes point.x en lugar de recordar que point[0] es la x. Sigue siendo completamente inmutable y actúa como una tupla normal en todo lo demás. Tu yo futuro leyendo el código te lo agradecerá por los nombres.
JunoTuplas con nombre `namedtuple` construye una clase similar a una tupla con acceso a atributos con nombre, inmutable y autodocumentada, con menos peso que una clase completa. Úsala en el momento en que las posiciones de una tupla simple necesitarían un comentario para explicarse. Pasado eso, `dataclass` es el siguiente paso moderno.
JunoTuplas con nombre `namedtuple` te da una subclase de tupla con campos con nombre al mismo costo de memoria, más `_replace()` y `_asdict()` de forma gratuita. Una vez que quieras valores predeterminados, métodos, o anotaciones de tipo reales, salta a `typing.NamedTuple` o `dataclass` en lugar de luchar contra la fábrica.

namedtuple vive en la biblioteca estándar, por lo que necesita una importación primero: from collections import namedtuple. Las importaciones reciben tratamiento completo en el capítulo Módulos.

python
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
Player = namedtuple("Player", ["name", "score", "level"])

p = Point(10, 20)
p.x    # 10
p.y    # 20

carlos = Player("Carlos", 87, 5)
carlos.name    # "Carlos"
carlos.score   # 87

Conjuntos

Un conjunto es una colección de valores únicos sin orden garantizado. Agregar el mismo valor dos veces no hace nada: un conjunto mantiene solo una copia de cada elemento. Usa llaves para un conjunto con elementos, o set() para crear un conjunto vacío.

set es una colección desordenada que rechaza automáticamente duplicados. La prueba de pertenencia es O(1) sin importar el tamaño, lo que lo hace la herramienta correcta cada vez que necesites verificar si un valor existe en una colección grande. Nota: {} crea un diccionario vacío, no un conjunto vacío; usa set() para eso.

Un set mantiene valores únicos y hashables y se construye sobre una tabla hash, la misma estructura de búsqueda por contenido que un diccionario. Eso te compra O(1) promedio para pertenencia, inserción y eliminación (el costo se mantiene plano a medida que el conjunto crece, en lugar de subir con su tamaño). Dos consecuencias para planificar: solo los valores hashables pueden entrar, así que int, str y tuple están bien pero list, dict y set no lo están, y el orden de iteración sigue las posiciones hash internas, así que nunca confíes en que sea estable. Una trampa de sintaxis: {} es un diccionario vacío, no un conjunto vacío; usa set().

JunoConjuntos Un conjunto mantiene solo valores únicos, así que agregar algo que ya esté ahí no hace nada en absoluto, sin error, sin duplicado. Construye uno con llaves e elementos, pero usa set() para uno vacío, porque {} es secretamente un diccionario vacío. Ese último detalle atrapa a casi todos.
JunoConjuntos Un conjunto elimina automáticamente duplicados y prueba la pertenencia en O(1) sin importar cuán grande sea, lo que lo hace la herramienta correcta para "¿está esto aquí?" en una colección grande. Ten cuidado con la trampa del conjunto vacío: `{}` es un diccionario, así que usa `set()`.
JunoConjuntos Un conjunto es una tabla hash de valores únicos y hashables: O(1) pertenencia, inserción y eliminación, con el costo de que solo los elementos hashables entran y el orden de iteración nunca es estable. No confíes en ese orden, y recuerda que `{}` es un diccionario, así que usa `set()` cuando quieras un conjunto vacío.
python
tags = {"python", "beginner", "tutorial"}
numbers = {1, 2, 3, 4, 5}
empty = set()    # NO {} (eso es un diccionario vacío)

Agregar el mismo valor dos veces no cambia el conjunto:

python
tags.add("python")   # tags no cambia, "python" ya está en él

Cuándo usar un conjunto

Los conjuntos son la herramienta correcta para tres cosas: eliminar duplicados de una lista, verificar rápidamente si algo está en una colección grande y comparar dos grupos para encontrar lo que comparten o en qué difieren.

Tres casos de uso distintos impulsan el uso de conjuntos: deduplicación (automática en la inserción), prueba de pertenencia O(1) (versus O(n) para list), y álgebra de conjuntos (|, &, -, ^). Cuando la colección es grande y verificas la pertenencia frecuentemente, la diferencia de rendimiento es sustancial.

Tres casos de uso surgen directamente de la tabla hash: singularidad (duplicados rechazados en la inserción), pertenencia O(1) (el costo de in se mantiene plano a medida que el conjunto crece), y álgebra de conjuntos (|, &, -, ^). La prueba de pertenencia es la que se paga en código real: in en un conjunto de 10,000 elementos es tan rápido como en uno de 10, donde la misma verificación en una lista es O(n) y se ralentiza linealmente. Cuando te encuentras haciendo x in some_list repetidamente contra una lista grande, convertirla a un conjunto una sola vez es usualmente la solución.

JunoCuándo usar un conjunto Tres trabajos en los que los conjuntos son excelentes: eliminar duplicados de una lista, verificar rápidamente si algo está en una colección grande y comparar dos grupos para ver qué comparten o en qué difieren. Si alguno de esos es lo que estás haciendo, un conjunto es probablemente la herramienta.
JunoCuándo usar un conjunto Tres impulsores: deduplicación en la inserción, pertenencia O(1) versus O(n) para una lista, y álgebra de conjuntos con `|`, `&`, `-`, `^`. Cuanto más grande es la colección y más frecuentemente verificas `in`, más se adelanta el conjunto.
JunoCuándo usar un conjunto Singularidad, O(1) `in`, y álgebra de conjuntos, todo saliendo de la tabla hash. El beneficio de la pertenencia es el práctico: `x in big_list` repetido es O(n) cada vez, por lo que convertir esa lista a un conjunto una sola vez es la solución habitual.
python
# Eliminar duplicados de una lista
raw = ["gato", "perro", "gato", "pájaro", "perro", "gato"]
unique = list(set(raw))   # ["gato", "perro", "pájaro"] (orden no garantizado)
python
# Verificación de pertenencia rápida
valid_codes = {"USD", "EUR", "GBP", "JPY"}
code = "EUR"

if code in valid_codes:    # búsqueda instantánea, incluso con miles de códigos
    print("Válido")

Operaciones de conjuntos

Los conjuntos admiten las mismas operaciones que aprendiste en matemáticas: unión (todo en cualquiera de los conjuntos), intersección (solo lo que ambos conjuntos comparten) y diferencia (lo que uno tiene que el otro no). Python usa símbolos de operador para estos, y cada uno tiene un equivalente de método.

Los operadores de conjunto de Python reflejan la notación matemática: | para unión, & para intersección, - para diferencia, ^ para diferencia simétrica. Cada operador tiene una forma de método (.union(), .intersection(), etc.) que también acepta cualquier iterable, no solo conjuntos.

Las formas de operador (|, &, -, ^) requieren que ambos lados sean conjuntos y lanzan TypeError si alguno es algo más, como una lista. Las formas de método (.union(), .intersection(), y el resto) son más flexibles: aceptan cualquier iterable y lo convierten para ti, así que a.union([1, 2]) funciona donde a | [1, 2] falla. Esa diferencia es la que recordar cuando una operación se interrumpe. También hay formas en su lugar (|=, &=, -=, ^=) que actualizan el conjunto izquierdo en lugar de devolver uno nuevo, lo mismo que llamar a .update(), .intersection_update(), y así sucesivamente.

JunoOperaciones de conjuntos Cuatro operaciones de la clase de matemáticas: `|` es unión (en cualquiera), `&` es intersección (en ambas), `-` es diferencia (en una pero no en la otra), y `^` es diferencia simétrica (en una pero no en ambas). Cada una también tiene un método escrito como `.union()` si prefieres palabras a símbolos.
JunoOperaciones de conjuntos `|` unión, `&` intersección, `-` diferencia, `^` diferencia simétrica. Las formas de método (`.union()` y amigos) hacen lo mismo pero aceptan cualquier iterable, no solo un conjunto, que los operadores no harán.
JunoOperaciones de conjuntos Los operadores quieren que ambos lados sean conjuntos y lanzan `TypeError` en caso contrario; las formas de método toman cualquier iterable, así que `a.union([1, 2])` funciona donde `a | [1, 2]` no. Las formas en su lugar (`|=`, `&=`) actualizan en su lugar en lugar de devolver un nuevo conjunto.
python
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

a | b    # {1, 2, 3, 4, 5, 6}   (unión: todo en cualquiera)
a & b    # {3, 4}               (intersección: solo en ambos)
a - b    # {1, 2}               (diferencia: en a pero no en b)
b - a    # {5, 6}               (diferencia al revés)
a ^ b    # {1, 2, 5, 6}        (diferencia simétrica: en uno pero no en ambos)

Estos también tienen formas de método: .union(), .intersection(), .difference(), .symmetric_difference().

Modificar conjuntos

Los conjuntos son mutables. .add() agrega un elemento. .update() agrega varios a la vez de cualquier lista u otro iterable. .remove() elimina un elemento pero lanza un error si no está ahí. .discard() elimina silenciosamente si el elemento existe y no hace nada si no existe.

.add() es O(1) promedio. .update() acepta cualquier iterable y es equivalente a llamar a .add() en un bucle. .remove() lanza KeyError en un fallo, mientras que .discard() es la opción segura cuando la presencia es incierta. .pop() elimina un elemento arbitrario, no el "último" ya que los conjuntos no tienen orden.

.add(x) inserta un elemento en O(1) promedio; .update(iterable) agrega muchos y es lo mismo que |=. El par que muerde a la gente es .remove() versus .discard(): ambos eliminan un elemento, pero .remove() lanza KeyError cuando el elemento está ausente mientras que .discard() simplemente no hace nada. Usa .discard() cada vez que la presencia sea incierta, para que un elemento faltante no sea una excepción que tengas que envolver. .pop() devuelve y elimina algún elemento, pero cuál es indefinido: un conjunto no tiene orden, así que nunca trates .pop() como si tomara el elemento "último".

JunoModificar conjuntos.add() coloca un elemento, .update() agrega un lote completo de una lista u otro iterable. El par a mantener en línea es la eliminación: .remove() produce error si el elemento no está, mientras que .discard() se encoge de hombros y sigue adelante. Cuando no estés seguro de que esté en el conjunto, .discard() te salva de un error extraño.
JunoModificar conjuntos.add() para uno, .update() para muchos de cualquier iterable. .remove() lanza KeyError en un fallo; .discard() es la versión segura que lo ignora. Y .pop() extrae un elemento arbitrario, no uno "último", ya que los conjuntos no tienen orden.
JunoModificar conjuntos.add() es O(1), .update() es `|=`. La trampa es .remove() lanzando KeyError donde .discard() se queda callado, así que por defecto usa .discard() cuando la presencia es incierta. .pop() devuelve un elemento indefinido; nunca lo leas como el último.
python
tags = {"python", "beginner"}

tags.add("tutorial")          # agrega un elemento
tags.update(["web", "api"])   # agrega múltiples elementos de cualquier iterable
tags.remove("beginner")       # elimina, lanza KeyError si no se encuentra
tags.discard("missing")       # elimina, sin error si no se encuentra
tags.pop()                    # elimina y devuelve un elemento arbitrario
tags.clear()                  # elimina todo

Usa .discard() cuando no estés seguro de si el elemento existe.

Conjuntos congelados

Un conjunto congelado es un conjunto que no puedes modificar después de crearlo. La razón principal para usar uno: los conjuntos congelados son hashables, por lo que pueden usarse como claves de diccionario o almacenarse dentro de otros conjuntos.

frozenset es la contraparte inmutable de set. Admite todas las operaciones de lectura y álgebra de conjuntos pero no mutación. Su inmutabilidad la hace hashable, lo que significa que es válida como clave de diccionario o como miembro dentro de otro conjunto.

Un frozenset es un set que no puedes cambiar después de crearlo. Debido a que nada dentro se mueve, obtiene un hash estable y es en sí mismo hashable, por lo que puede ser una clave de diccionario o un miembro de otro conjunto, ninguno de los cuales un set simple puede hacer. Todo lo que lee o devuelve una nueva colección sigue funcionando (pertenencia, los operadores de álgebra de conjuntos), mientras que los métodos mutantes (add, remove, y el resto) se van. El caso en el que encaja limpiamente: una tabla de búsqueda constante que no debe cambiar mientras se ejecuta el programa y puede necesitar vivir dentro de un diccionario o conjunto.

JunoConjuntos congelados Un conjunto congelado es un conjunto que no puedes cambiar una vez que se hace. Ese estado bloqueado es lo que lo hace hashable, así que a diferencia de un conjunto normal puedes usar uno como clave de diccionario o metérlo dentro de otro conjunto. Úsalo cuando tengas un grupo fijo de valores permitidos que nunca deben cambiar.
JunoConjuntos congelados `frozenset` es el `set` inmutable: todas las lecturas y álgebra de conjuntos, ninguna mutación. Su inmutabilidad compra hashability, así que funciona como clave de diccionario o miembro de otro conjunto donde un `set` simple lanza. Buen ajuste para una tabla de búsqueda constante.
JunoConjuntos congelados `frozenset` tiene un hash estable, por lo que va donde un `set` no puede: claves de diccionario, miembros de otro conjunto. Las lecturas y el álgebra de conjuntos se quedan, los métodos de mutación se van. El uso limpio es una tabla de búsqueda constante que no debe cambiar en tiempo de ejecución.
python
valid_statuses = frozenset({"active", "paused", "deleted"})
valid_statuses.add("archived")    # AttributeError, frozenset es inmutable

Elegir la colección correcta

Cuatro tipos, cada uno con un papel claro. Pregúntate qué necesitas hacer con los datos y la opción correcta generalmente sigue.

La elección entre tipos de colección se trata de qué operaciones importan y qué restricciones tienen tus datos: mutabilidad, ordenamiento, manejo de duplicados y estrategia de búsqueda.

La elección es parte rendimiento, parte significado. dict y set dan búsqueda O(1) promedio a través de hashing, por lo que verificar la pertenencia se mantiene rápida a medida que crecen. list y tuple dan acceso O(1) por índice pero pertenencia O(n), donde in se ralentiza con el tamaño. El otro eje es lo que señala el tipo: una tuple es un registro fijo cuya inmutabilidad compra hashability, donde una list es una secuencia de longitud variable. Cuando necesitas una colección hashable para anidar dentro de un diccionario o conjunto, tuple y frozenset son los dos tipos incorporados que pueden hacerlo.

JunoElegir la colección correcta Cuatro tipos, cuatro trabajos claros: listas para cosas ordenadas que cambiarás, tuplas para registros fijos, conjuntos para valores únicos y verificaciones rápidas de "¿está aquí?", diccionarios para buscar cosas por nombre. Pregúntate qué necesitas hacer con los datos y la correcta generalmente sigue.
JunoElegir la colección correcta Elige según lo que los datos necesiten: mutabilidad, orden, si los duplicados importan, y cómo buscas cosas. Lista para una secuencia ordenada que mutas, tupla para un registro fijo, conjunto para valores únicos y pertenencia O(1), diccionario para búsqueda clave-valor.
JunoElegir la colección correcta Mitad rendimiento, mitad significado: `dict` y `set` dan búsqueda O(1), `list` y `tuple` dan O(1) por índice pero pertenencia O(n). Una `tuple` también señala "registro fijo" y compra hashability. Cuando necesitas una colección hashable para anidar, `tuple` y `frozenset` son tus dos opciones.
listtuplesetdict
OrdenadoNoSí (orden de inserción)
MutableNo
DuplicadosNoNo (claves)
Acceso porÍndiceÍndicen/aClave
Usar cuandoSecuencia ordenada, modificableRegistro fijoValores únicos, pertenencia rápidaBúsqueda clave-valor

Una regla de decisión rápida:

  • ¿Necesitas buscar algo por nombre? → dict
  • ¿Necesitas una colección ordenada que modificarás? → list
  • ¿Tienes un grupo fijo de valores relacionados? → tuple
  • ¿Necesitas valores únicos o pruebas de pertenencia rápidas? → set

En la práctica

Usando tuplas para almacenar registros fijos y un conjunto para rastrear valores únicos:

python
home = (51.5074, -0.1278)   # latitud, longitud
office = (51.5155, -0.0922)

home_lat, home_lon = home
print(f"Hogar: {home_lat}, {home_lon}")

# Rastrear visitantes únicos con un conjunto
visitors = set()
visitors.add("carlos")
visitors.add("maría")
visitors.add("carlos")    # ya está en el conjunto, silenciosamente ignorado
visitors.add("luis")

print(f"Visitantes únicos: {len(visitors)}")
print(f"carlos visitó: {'carlos' in visitors}")
print(f"juan visitó:  {'juan' in visitors}")

Usando conjuntos para rastrear lo que ya ha sido procesado y calcular el trabajo restante:

python
already_processed = {"report_ene.csv", "report_feb.csv"}
all_files = {"report_ene.csv", "report_feb.csv", "report_mar.csv", "report_abr.csv"}

to_process = all_files - already_processed
print(f"Archivos para procesar: {sorted(to_process)}")

for filename in sorted(to_process):
    print(f"Procesando {filename}...")
    already_processed.add(filename)

print(f"Hecho. Total procesado: {len(already_processed)}")

Usando frozenset para tablas de búsqueda constantes y demostrando pruebas de pertenencia O(1) con álgebra de conjuntos:

python
ALLOWED_METHODS = frozenset({"GET", "POST", "PUT", "PATCH", "DELETE"})
SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})

# El álgebra de conjuntos en frozensets devuelve un conjunto normal
unsafe_allowed = ALLOWED_METHODS - SAFE_METHODS
print(f"Métodos permitidos no seguros: {unsafe_allowed}")

# frozenset es hashable, así que puede almacenarse en un conjunto (un conjunto simple no puede)
method_groups = {
    frozenset({"GET", "HEAD", "OPTIONS"}),
    frozenset({"POST", "PUT", "PATCH"}),
    frozenset({"DELETE"}),
}
print(f"Grupos de métodos: {len(method_groups)}")

method = "POST"
print(f"Permitido: {method in ALLOWED_METHODS}")
print(f"Seguro:    {method in SAFE_METHODS}")

frozenset conlleva búsqueda O(1) y puede almacenarse en cualquier lugar donde se requiera un tipo hashable. El álgebra de conjuntos en dos objetos frozenset devuelve un set simple; envuelve el resultado en frozenset() para mantenerlo inmutable.