Tipado en Python: qué son los type hints, cuándo usarlos y por qué importan con la IA
Python no te pide declarar qué tipo de dato guarda una variable. Tampoco qué devuelve una función. Por eso escribes rápido y pruebas ideas en minutos.
El problema llega cuando el proyecto crece, o cuando tu código recibe datos de una API o de un modelo de IA. Una función que a veces devuelve un diccionario y a veces None termina fallando en el peor momento, con el clásico:
AttributeError: 'NoneType' object has no attribute 'get'
Ahí entra el tipado en Python, también llamado type hints o anotaciones de tipo. En esta guía vas a ver qué hacen los dos puntos (:) y la flecha (->), cuándo vale la pena tipar y cuándo es trabajo de más, cómo se escribe desde Python 3.10 y por qué los tipos se volvieron clave para trabajar con modelos de IA y agentes.
Para quién es: para quien programa en Python (o está aprendiendo) y quiere código que falle menos. También para quien conecta Python con un modelo de IA y necesita respuestas con un formato fijo.
En corto
- Los dos puntos (
:) dicen qué tipo de dato espera una variable o un parámetro:nombre: str. - La flecha (
->) dice qué devuelve una función:def calcular() -> float:. - Python no revisa los tipos cuando corre el programa. Los usan tu editor, verificadores como
mypyopyrighty librerías como Pydantic. - Tipa: funciones que se reutilizan, APIs, datos que entran de afuera y llamadas a modelos de IA.
- No tipes: scripts de una sola vez y variables obvias como
total = 0. - Con la IA: las APIs de OpenAI y Anthropic convierten una clase tipada en un esquema y obligan al modelo a responder con ese formato. Eso se llama salidas estructuradas. Los agentes también leen tipos para saber cómo usar sus herramientas (tool calling y skills de agentes).
¿Para qué sirven los dos puntos (:) y la flecha (->)?
Para empezar no necesitas instalar nada. Todo se reduce a dos símbolos:
def obtener_saludo(nombre: str, veces: int = 1) -> str:
return f"Hola, {nombre}! " * veces
Los dos puntos (:): qué tipo esperas
Con nombre: str le dices a quien lea el código (una persona, tu editor o un agente de IA) que nombre debe ser texto. Si el parámetro tiene un valor por defecto, va después del tipo: veces: int = 1.
También puedes tipar variables sueltas:
temperatura: float = 24.5
activo: bool = True
La flecha (->): qué devuelve la función
La flecha va justo antes de los dos puntos que abren la función. -> str: quiere decir “esta función devuelve texto”.
Si la función solo hace algo (imprimir, guardar un archivo) y no devuelve nada útil, su retorno es None:
def registrar_evento(mensaje: str) -> None:
print(f"[LOG]: {mensaje}")
Ojo: Python no frena el programa por un tipo equivocado
Si vienes de Java, C++ o Rust, esto sorprende. Python no revisa las anotaciones cuando corre el código:
# La función espera texto, le pasas un número y corre igual:
obtener_saludo(nombre=123) # devuelve 'Hola, 123! '
El tipo dice str, le pasaste un int y Python no protestó. Si algo falla, falla más adelante, por lo que hiciste con el dato, no por la anotación.
Entonces, ¿para qué escribirlas?
- Tu editor te avisa antes de ejecutar: VS Code, Cursor o PyCharm leen los tipos mientras escribes. Si llamas
.upper()sobre algo que puede serNone, te lo marcan antes de correr el programa. En VS Code ese trabajo lo hace Pylance, pero su revisión de tipos viene apagada (off) por defecto; más abajo te digo cómo prenderla. - Documentación que no se desactualiza: un comentario puede quedar viejo después de un cambio. Una firma tipada te dice qué entra y qué sale, y el verificador te avisa si deja de cumplirse.
- Revisión del proyecto completo:
mypyopyrightse corren en la terminal o en tu pipeline de despliegue (CI/CD) para encontrar funciones que ya no cumplen lo que prometen en todo el repositorio.
¿Cuándo vale la pena tipar y cuándo no?
El error típico de quien descubre los tipos es tipar todo: cada variable de un for, cada script de 15 líneas. Tipar cada variable de un script de 15 líneas es como ponerle casco a una bicicleta estática.
Esta tabla te ayuda a decidir:
Tabla 1: Cuándo tipar en Python y cuándo no
| Contexto | ¿Tipar? | Por qué |
|---|---|---|
| Scripts de una sola vez | No | Si vas a renombrar 100 archivos una sola vez (como en el script para renombrar archivos con Python), lo que importa es terminar rápido. |
| Variables obvias | No | contador: int = 0 o cliente: str = "Ana" sobran. El verificador ya deduce que son un entero y un texto. |
| Funciones que se reutilizan | Sí | Si una función vive en un módulo que otras partes usan, tipa sus parámetros y su retorno. Así nadie adivina qué recibe dentro de seis meses. |
| Datos que entran o salen (APIs, archivos, webhooks) | Sí | Leer una base de datos, un JSON externo o un webhook sin tipos claros deja pasar datos dañados al resto del código. |
| Llamadas a modelos de IA | Sí, siempre | Un modelo puede responder distinto cada vez. Si no fijas el formato de salida, tarde o temprano rompe tu código. |
| Código que escribe un agente de IA | Sí, siempre | El agente lee las firmas para saber qué puede cambiar sin romper el resto de la aplicación. |
[!TIP] Regla práctica: tipa los bordes de tu código: lo que entra de afuera y lo que devuelven tus funciones. Adentro de cada función, deja que Python deduzca los tipos solo.
Cómo se tipa hoy: la sintaxis desde Python 3.10
Antes había que importar casi todo desde el módulo typing:
# Sintaxis antigua
from typing import List, Dict, Optional, Union
def procesar_datos(valores: List[Union[int, float]]) -> Optional[Dict[str, float]]:
...
Python 3.9 permitió escribir list y dict directamente (PEP 585). Python 3.10 agregó la barra vertical | para decir “esto o aquello” (PEP 604). Con las dos cosas ya no necesitas importar List, Dict, Union ni Optional:
# Sintaxis moderna (Python 3.10 en adelante)
def procesar_datos(valores: list[int | float]) -> dict[str, float] | None:
...
La barra vertical (|): esto o aquello
- Un valor que puede ser entero o decimal:
int | float. - Un valor que puede venir vacío:
str | None. Antes se escribíaOptional[str].
Literal: solo unas opciones
Muchas veces una función no acepta cualquier texto, solo unos valores concretos. Para eso está Literal:
from typing import Literal
EtapaEmbudo = Literal["tofu", "mofu", "bofu"]
def clasificar_articulo(visitas: int, intencion: EtapaEmbudo) -> str:
return f"Contenido {intencion} con {visitas} lecturas."
Si alguien escribe clasificar_articulo(100, "conversion"), el editor lo marca: "conversion" no está en la lista.
TypedDict: diccionarios con llaves fijas
Con dict[str, str] solo dices de qué tipo son las llaves y los valores en general. Si quieres que el diccionario tenga llaves concretas, cada una con su tipo, usas TypedDict:
from typing import TypedDict
class MetadatosArticulo(TypedDict):
slug: str
palabras: int
indexado: bool
def auditar_post(url: str) -> MetadatosArticulo:
return {
"slug": url.strip("/"),
"palabras": 1420,
"indexado": True
}
Tu editor te autocompleta las llaves y el verificador te avisa si falta una. Python, igual que antes, no lo revisa al correr.
¿Por qué los tipos importan tanto con la IA?
Hasta aquí, el tipado es una herramienta clásica de programación. Con los modelos de lenguaje (LLM) ganó otro trabajo: es la forma de decirle al modelo qué formato debe tener su respuesta.
La razón es simple. Un modelo, por defecto, devuelve texto libre. Y el texto libre es difícil de leer para un programa: a veces llega el JSON solo y a veces con un “Claro, aquí está tu JSON” adelante.
flowchart TD
PROMPT["<strong>Prompt</strong><br/>Lo que le pides al modelo"]
LLM["<strong>Modelo de IA (LLM)</strong><br/>Genera la respuesta token por token"]
TEXT["<strong>Texto libre</strong><br/>'Claro, aquí está el JSON: ...'<br/><em>Tu código puede no saber leerlo</em>"]
SCHEMA["<strong>Esquema tipado</strong><br/>Clase de Pydantic convertida en JSON Schema<br/><em>El modelo solo puede escribir lo que encaja</em>"]
DB["<strong>JSON con el formato pedido</strong><br/>Listo para tu base de datos o tu página"]
PROMPT --> LLM
LLM -- "Sin esquema" --> TEXT
LLM -- "Con esquema" --> SCHEMA
SCHEMA --> DB
Salidas estructuradas con Pydantic
OpenAI y Anthropic ofrecen salidas estructuradas (Structured Outputs) en sus APIs. Tú les pasas un esquema y el modelo solo puede generar respuestas que lo cumplan.
Ese esquema lo escribes como una clase tipada con Pydantic, una librería de Python que valida datos:
from pydantic import BaseModel, Field
from typing import Literal
class AnalisisBusquedaSEO(BaseModel):
intencion_busqueda: Literal["informativa", "transaccional", "navegacional"]
puntuacion_relevancia: float = Field(ge=0.0, le=1.0, description="Puntaje de 0 a 1")
palabras_clave: list[str]
resumen_bluf: str = Field(description="Resumen factual directo en 1 oración")
Con los SDK oficiales le pasas la clase directamente: client.responses.parse(text_format=...) en OpenAI o client.messages.parse(output_format=...) en Anthropic. Por debajo pasa esto:
- El SDK convierte tu clase en un JSON Schema: un documento que describe qué campos lleva el JSON y de qué tipo es cada uno.
- El modelo queda limitado a ese esquema:
intencion_busquedasolo puede ser uno de los tres valores ypuntuacion_relevanciatiene que ser un número. - Recibes un objeto de Python ya validado: listo para guardar en tu base de datos o mostrar en tu sitio.
Hay dos casos que la documentación de OpenAI menciona: si el modelo se niega a responder o si la respuesta se corta por el límite de tokens, puede no cumplir el esquema. Revisa esos casos en tu código.
Herramientas de un agente (tool calling)
Un agente de IA (en Claude Code, en Antigravity o en un servidor MCP que tú construyes) necesita herramientas para hacer cosas: leer archivos, llamar una API, comprimir una imagen. Que el modelo pida usar una de esas herramientas se llama tool calling.
En Python, una herramienta suele ser una función normal. El modelo no ve tu código: ve una descripción con el nombre, los parámetros y sus tipos. El SDK oficial de MCP para Python, por ejemplo, arma esa descripción a partir de la firma tipada. Por eso los tipos y el docstring tienen que ser claros:
def optimizar_imagen_web(
ruta_origen: str,
formato_destino: Literal["webp", "avif"] = "webp",
calidad: int = 85
) -> dict[str, str | int]:
"""
Comprime y convierte una imagen al formato web especificado.
:param ruta_origen: Ruta absoluta o relativa de la imagen fuente.
:param formato_destino: Formato de salida comprimido ('webp' o 'avif').
:param calidad: Nivel de compresión entre 1 y 100.
:return: Diccionario con la nueva ruta y el tamaño final en bytes.
"""
# Lógica interna de compresión...
return {"ruta": "foto.webp", "bytes": 45000}
Con formato_destino: Literal["webp", "avif"] = "webp", el modelo sabe que no debe pedir "png" ni "gif". Y si activas el modo estricto (strict: true en la API de Anthropic, por ejemplo), la API valida que cada llamada cumpla el esquema. Un tipo así es una instrucción mucho más precisa que una frase en el prompt.
Una red de seguridad para el vibe coding
El vibe coding es programar pidiéndole cambios a un agente de IA. Como explico en la guía sobre qué es Git en la era del vibe coding, es rápido pero tiene sus riesgos. El agente puede cambiar una función y, sin avisarte, hacer que devuelva una lista donde antes devolvía un diccionario.
- Sin tipos: te enteras cuando un usuario real hace clic y algo se rompe en producción.
- Con tipos y un verificador: si el agente corre
pyrightomypydespués de cada cambio, el error aparece de inmediato y el agente lo puede corregir antes de que apruebes el cambio o hagasgit commit.
Para que el agente lo corra siempre, pídeselo en sus instrucciones, por ejemplo en el archivo AGENTS.md o en una skill.
Cómo se conecta con otros temas del blog
- IA y agentes: los tipos son la base para trabajar por módulos. Lee cómo crear skills para agentes de IA paso a paso y la guía de desarrollo modular con IA.
- Automatización: los scripts que procesan archivos también ganan con tipos. Mira el tutorial para convertir imágenes HEIC a JPG con Python sin perder metadatos.
- SEO técnico: si generas datos estructurados de Schema.org con un script, validar los diccionarios con tipos evita errores de formato. Un marcado con errores deja de ser elegible para resultados enriquecidos, y Search Console te lo muestra en sus informes. Mira el experimento de marcado JSON-LD con IA.
Cómo empezar hoy
- Usa Python 3.10 o más nuevo: escribe
list[str],dict[str, int]ystr | Nonesin importar nada detyping. - Tipa los bordes: entradas y salidas de tus funciones principales, endpoints y modelos de datos. Lo de adentro, déjalo.
- Usa
Literalpara opciones fijas: si un parámetro solo acepta 2 o 3 valores, no lo dejes comostra secas. - Usa Pydantic para hablar con un modelo de IA: no le pidas “respóndeme en JSON con este formato” en el prompt. Pásale un esquema tipado con las salidas estructuradas de la API.
- Prende la revisión de tipos en tu editor: en VS Code, cambia
python.analysis.typeCheckingModea"basic". Desde ahí el editor te avisa mientras escribes.
Fuentes
Consultadas el 27 de octubre de 2026.
- PEP 484, Type Hints: https://peps.python.org/pep-0484/
- PEP 585, genéricos en colecciones estándar (Python 3.9): https://peps.python.org/pep-0585/
- PEP 604, uniones con
X | Y(Python 3.10): https://peps.python.org/pep-0604/ - PEP 586,
Literal: https://peps.python.org/pep-0586/ - PEP 589,
TypedDict: https://peps.python.org/pep-0589/ - OpenAI, Structured Outputs: https://developers.openai.com/api/docs/guides/structured-outputs
- Anthropic, Structured outputs: https://platform.claude.com/docs/en/build-with-claude/structured-outputs
- SDK oficial de MCP para Python: https://github.com/modelcontextprotocol/python-sdk
- Pylance, configuración
typeCheckingMode: https://github.com/microsoft/pylance-release
Más allá del código: estrategia, escalabilidad e impacto
Lecturas clave para conectar la arquitectura técnica con el valor de negocio y rentabilidad.

Análisis de la Decisions API de OpenAI (beta): respuestas tipadas con probabilidades, entrada de imágenes y precio por token, frente a Jev y Laya.

Guía en lenguaje sencillo de DeepSeek-V4.1-Flash: caché KV más pequeña, expertos activados por token, benchmarks, pesos abiertos y la actualización 1017.

Qué es una skill de agente, cuándo elegirla frente a tools, MCP o AGENTS.md, buenas prácticas para escribirla y un ejemplo completo y validado que puedes copiar.
