CAP 05 · LEC 02·Type hints

typing: list, dict, tuple, Optional y Union

Anotar colecciones es donde los type hints se vuelven poderosos. list[int], dict[str, Any], tuple[int, str, bool] — Python moderno (3.9+) permite la sintaxis inline sin imports.

● INTERMEDIO9 min lectura4 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

list[T] y dict[K, V]

Antes de Python 3.9, para anotar una lista de enteros necesitabas from typing import List y escribir List[int]. Desde 3.9, puedes usar directamente list[int] en minúsculas — más limpio y sin imports.

# Python 3.9+: sintaxis inline (preferida) def sumar_lista(numeros: list[int]) -> int: return sum(numeros) def obtener_nombres(usuarios: list[dict[str, str]]) -> list[str]: return [u["nombre"] for u in usuarios] # dict[K, V]: clave y valor tipados def contar_palabras(texto: str) -> dict[str, int]: conteo: dict[str, int] = {} for palabra in texto.split(): conteo[palabra] = conteo.get(palabra, 0) + 1 return conteo print(sumar_lista([1, 2, 3, 4])) # 10 print(contar_palabras("hola mundo hola")) # {'hola': 2, 'mundo': 1}
Salida10 {'hola': 2, 'mundo': 1}
Python 3.8 (typing)Python 3.9+ (inline)
from typing import List List[int]list[int]
from typing import Dict Dict[str, int]dict[str, int]
from typing import Tuple Tuple[int, str]tuple[int, str]

Para diccionarios con valores de cualquier tipo puedes usar dict[str, object] o dict[str, Any] si importas Any:

from typing import Any # Configuración heterogénea: valores de distinto tipo Config = dict[str, Any] def cargar_config() -> Config: return { "host": "localhost", "puerto": 5432, "debug": True, "timeout": 30.5, } config = cargar_config() print(config["puerto"]) # 5432
Salida5432

tuple — longitud y tipos fijos

tuple en Python puede usarse de dos formas distintas como tipo:

  1. Tupla de longitud fija: cada posición tiene su tipo — tuple[int, str, bool]
  2. Tupla de longitud variable (homogénea): todos los elementos del mismo tipo — tuple[int, ...]
# Tupla de longitud fija: (id, nombre, activo) def obtener_usuario(user_id: int) -> tuple[int, str, bool]: return (user_id, "Ana García", True) # Desestructuración con tipos correctos uid, nombre, activo = obtener_usuario(1) print(f"{uid}: {nombre} — activo: {activo}") # 1: Ana García — activo: True # Tupla de longitud variable (homogénea) def obtener_puntuaciones() -> tuple[int, ...]: return (95, 87, 92, 78, 100) puntos = obtener_puntuaciones() print(sum(puntos) / len(puntos)) # 90.4
Salida1: Ana García — activo: True 90.4
tuple[] vs list[]

Usa list[T] cuando la colección puede crecer o cambiar. Usa tuple[T1, T2, ...] cuando la longitud es fija y cada posición tiene un significado específico (como retornar múltiples valores de una función).

Optional y Union — sintaxis moderna con |

Union[A, B] significa «este valor puede ser de tipo A o de tipo B». Con Python 3.10+, la sintaxis es simplemente A | B, mucho más legible.

from typing import Union, Optional # Union clásico (Python 3.8+) def parsear_id(valor: Union[str, int]) -> int: return int(valor) # Sintaxis moderna con | (Python 3.10+) def parsear_id_v2(valor: str | int) -> int: return int(valor) # Optional es exactamente Union[X, None] = X | None def buscar_producto(codigo: str) -> Optional[dict[str, str]]: catalogo = {"A01": {"nombre": "Teclado", "precio": "75.00"}} return catalogo.get(codigo) print(parsear_id_v2("42")) # 42 print(buscar_producto("A01")) # {'nombre': 'Teclado', 'precio': '75.00'} print(buscar_producto("Z99")) # None
Salida42 {'nombre': 'Teclado', 'precio': '75.00'} None

El operador | también funciona con más de dos tipos:

# Tres tipos posibles (raro, pero válido) def formatear_valor(valor: int | float | str) -> str: return str(valor) # Retorno que puede ser lista o None def obtener_etiquetas(post_id: int) -> list[str] | None: datos = {1: ["python", "tutorial"], 2: ["async"]} return datos.get(post_id) print(formatear_valor(3.14)) # 3.14 print(obtener_etiquetas(1)) # ['python', 'tutorial'] print(obtener_etiquetas(99)) # None
Salida3.14 ['python', 'tutorial'] None

Any y TypeVar básico

Any es la válvula de escape del sistema de tipos: le dice a mypy «confía en mí, este tipo está bien». Úsalo con cuidado — es contagioso.

from typing import Any, TypeVar # Any: sin restricciones de tipo (úsalo lo menos posible) def log_evento(dato: Any) -> None: print(f"[LOG] {dato!r}") log_evento(42) # [LOG] 42 log_evento("error") # [LOG] 'error' log_evento([1, 2, 3]) # [LOG] [1, 2, 3] # TypeVar: tipo genérico que mantiene la relación entre entrada y salida T = TypeVar("T") def primero(elementos: list[T]) -> T: return elementos[0] # mypy sabe que primero([1, 2, 3]) retorna int # y que primero(["a", "b"]) retorna str print(primero([10, 20, 30])) # 10 print(primero(["x", "y"])) # x
Salida[LOG] 42 [LOG] 'error' [LOG] [1, 2, 3] 10 x
Any desactiva la verificación

Cuando asignas Any a una variable, mypy deja de verificar operaciones sobre ella. Es útil para migrar código legacy, pero en código nuevo prefiere object si necesitas «cualquier cosa», y TypeVar si necesitas que el tipo se propague.

Practica