El Modelo Mental de Verificación de Tipos
Las sugerencias de tipo son anotaciones que adjuntas a variables, parámetros y valores de retorno.
Describen la forma de los datos que una pieza de código espera y produce.
El propio intérprete hace muy poco con ellas en tiempo de ejecución.
El trabajo real lo realiza un programa separado, un verificador de tipos estático como mypy o pyright, que lee tu código fuente, construye un modelo del tipo de cada valor y reporta dónde falla ese modelo.
Conceptos Básicos de Sugerencias de Tipo muestra la sintaxis; Protocolos y Tipificación Estructural, Genéricos y TypeVar y Estrategia de Tipificación Gradual cubren cada faceta en profundidad.
Esta página es la capa subyacente a todas ellas: qué está haciendo realmente un verificador y por qué es un tipo de seguridad fundamentalmente diferente al que proporciona el intérprete.
Resumen
- Una sugerencia de tipo es metadato leído por un verificador de tipos estático externo, no una restricción en tiempo de ejecución impuesta por el propio intérprete de Python.
- Por Qué Importa: Permite a un equipo detectar toda una clase de errores de desajuste de forma (argumento incorrecto,
Noneno manejado, un atributo mal escrito) antes de que el código se ejecute, sin renunciar al modelo de objetos dinámico de Python. - Conceptos Clave: anotación, verificador de tipos estático, tipificación gradual, tipificación estructural, tipificación nominal, eliminación de tipos.
- Cuándo Usar: Firmas de funciones públicas, datos que cruzan límites de módulos o servicios, cualquier base de código con más de un contribuidor, o en cualquier lugar donde una clase de error se repita debido a un valor de forma incorrecta.
- Limitaciones / Compensaciones: Las sugerencias no añaden seguridad en tiempo de ejecución por sí solas, el modelo de un verificador de un programa dinámico puede ser incorrecto o incompleto, y el modo estricto tiene fricción real contra el código legítimamente dinámico.
- Temas Relacionados: Configuración de mypy y pyright, tipificación estructural basada en Protocol, genéricos, estrategia de adopción gradual.
Fundamentos
Python incluyó la sintaxis de anotación (x: int, def f() -> str) años antes de que tuviera un significado oficial.
PEP 484 le dio un propósito a esa sintaxis: un vocabulario estándar que las herramientas externas pudieran leer para razonar sobre los tipos de un programa sin ejecutarlo.
Crucialmente, el comportamiento del intérprete no cambió.
def add(a: int, b: int) -> int: return a + b se ejecuta exactamente igual, ya sea que llames add(1, 2) o add("x", "y"); las anotaciones se almacenan en __annotations__ y, por lo demás, son inertes en tiempo de ejecución.
Una analogía útil es un plano revisado por un inspector de edificios antes de que comience la construcción.
La aprobación del inspector (una ejecución limpia de mypy) te dice que el diseño es sólido sobre el papel.
No impide físicamente que un equipo de construcción construya algo diferente del plano; el tiempo de ejecución de Python es ese equipo, y nunca consulta el plano mientras trabaja.
Es por eso que el enfoque de Python se llama tipificación gradual: una base de código puede mezclar módulos completamente anotados, módulos parcialmente anotados y código heredado completamente sin anotar, y el verificador razona sobre cada pieza con la información que tiene, recurriendo a un Any implícito donde falten sugerencias.
Esa única decisión de diseño es lo que hace que añadir tipos a una base de código grande existente sea factible, en lugar de una reescritura total o nada.
Mecánica e Interacciones
El trabajo real de un verificador de tipos es la inferencia estática: recorrer tu código fuente sin ejecutarlo, asignar un tipo a cada expresión y señalar los lugares donde una operación es inconsistente con los tipos que ha inferido.
Dos formas diferentes de decidir "si este valor satisface ese tipo" son importantes aquí, y mezclarlas es la fuente más común de confusión en esta sección.
La tipificación nominal pregunta sobre la ascendencia: un valor satisface un tipo si su clase es ese tipo o una subclase del mismo, verificado a través de la cadena de herencia explícita.
La tipificación estructural pregunta sobre la forma en su lugar: un valor satisface un tipo si tiene los métodos y atributos correctos, independientemente de qué clase sea realmente o de qué herede.
La jerarquía de clases integrada de Python es nominal por defecto, pero Protocol (PEP 544) añade tipificación estructural encima, que es por lo que un Protocol nunca requiere que las clases que lo satisfacen hereden de nada.
Tiempo de ejecución (lo que realmente se ejecuta) Capa estática (lo que ven mypy/pyright)
----------------------------------- --------------------------------------
def greet(name: str) -> str: greet: (str) -> str
return f"Hi {name}" name inferido como str dentro del cuerpo
greet(42) # se ejecuta, imprime "Hi 42" greet(42) # MARCADO: int no es str
La brecha en ese diagrama es el punto principal: el tiempo de ejecución nunca detiene greet(42), pero el modelo del programa del verificador sí lo hace, porque razona puramente a partir de los tipos declarados e inferidos, nunca a partir de lo que una ejecución dada hace realmente.
Los genéricos extienden esta misma maquinaria de inferencia a formas paramétricas: una list[int] y una list[str] son tipos diferentes para el verificador, aunque ambas sean, en tiempo de ejecución, la misma clase list.
TypeVar y la sintaxis type de PEP 695 te permiten escribir una función o clase cuyos tipos están vinculados entre parámetros (una TypeVar vinculada al tipo de elemento de un contenedor, por ejemplo), de modo que el verificador pueda verificar que lo que sale de una función genérica es consistente con lo que entró, sin que tengas que escribir esa función nueve veces para nueve tipos concretos.
Consideraciones Avanzadas y Aplicaciones
Dado que la inferencia solo es tan buena como la información disponible, los verificadores exponen controles de estrictez en lugar de un único interruptor de encendido/apagado: disallow_untyped_defs, warn_return_any, modo strict, anulaciones por módulo en pyproject.toml.
Una base de código grande casi nunca adopta el modo estricto completo en todas partes a la vez; en su lugar, ajusta los controles dial por dial, módulo por módulo, que es el tema completo de Estrategia de Tipificación Gradual.
Any es la vía de escape designada del verificador, y merece un respeto especial porque es contagioso: una vez que un valor se tipifica como Any, cada expresión construida a partir de él también es implícitamente Any, desactivando silenciosamente la verificación para todo lo que sigue hasta que un límite lo reanota.
La verificación de tipos estática y la validación en tiempo de ejecución también se confunden fácilmente, pero resuelven problemas diferentes.
Un verificador como mypy demuestra que tu código es internamente consistente dadas las tipos que declaraste; no puede verificar que los datos que llegan a través de HTTP o desde un archivo coincidan realmente con esos tipos, porque esos datos aún no existen cuando se ejecuta el verificador.
Esa es precisamente la brecha que llenan bibliotecas como Pydantic: validación en tiempo de ejecución en el límite, tipos estáticos en todas partes detrás de él, y por qué TypedDict documenta una forma JSON para el beneficio del verificador sin validar nada cuando el programa se ejecuta realmente.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Tipificación nominal (jerarquías de clases, ABCs) | Intención explícita, amigable con IDE, funciona bien para árboles de clases propios | Requiere herencia; incómodo para código de terceros u objetos ad hoc | Tu propio modelo de dominio con una relación real es-un |
Tipificación estructural (Protocol) | No requiere herencia; se adapta naturalmente a código duck-typed y de terceros | Fácil de subespecificar una forma; menos descubrible que una clase base concreta | Puertos/interfaces, puntos de prueba, adaptación de tipos externos |
| Validación en tiempo de ejecución (Pydantic) | Realmente aplica la forma cuando el programa se ejecuta, con mensajes de error reales | Costo en tiempo de ejecución; una segunda definición de esquema para mantener sincronizada con los tipos estáticos | Datos que cruzan un límite no confiable: HTTP, archivos, colas |
Sin sugerencias (Any / sin anotar) | Cero fricción, más rápido para prototipar | Ninguna garantía estática en absoluto; los errores aparecen como tracebacks de producción | Scripts desechables, cuadernos exploratorios |
La tipificación estática en Python también ha seguido evolucionando en lugar de quedarse quieta: la sintaxis de unión X | Y de PEP 604, la sintaxis genérica más concisa de PEP 695, y cada versión de mypy/pyright que reduce más patrones match/isinstance son parte de cerrar la brecha entre lo que el sistema de tipos puede expresar y lo que el Python idiomático ya parece.
Conceptos Erróneos Comunes
- "Las sugerencias de tipo convierten a Python en un lenguaje de tipificación estática." El tiempo de ejecución sigue siendo tan dinámico como antes; solo una herramienta separada y opcional impone algo, y solo cuando la ejecutas.
- "mypy o pyright que pasan significa que el código no puede fallar con entradas incorrectas." Una ejecución limpia del verificador demuestra la consistencia interna dadas las tipos que declaraste, no que los datos que llegan en tiempo de ejecución realmente coincidan con esas tipos; ese es un problema de validación, no uno de tipificación estática.
- "
Anyes una forma segura y neutral de silenciar un error del verificador."Anydeshabilita la verificación para ese valor y todo lo que se deriva de él, por lo que un soloAnyen un límite puede borrar silenciosamente la seguridad de tipos a través de toda una cadena de llamadas. - "
Protocoles solo una clase abstracta base con un decorador adicional." UnProtocolcoincide por forma, nunca por herencia; una clase lo satisface simplemente teniendo los métodos correctos, sin necesidad declass Foo(MyProtocol)en ninguna parte. - "El modo estricto es todo o nada, así que no vale la pena empezar." La estrictez es un conjunto de controles independientes, por módulo; una base de código puede ajustar un paquete a la vez indefinidamente sin tener que activar un único interruptor global.
Preguntas Frecuentes
¿Cambian las sugerencias de tipo cómo se ejecuta mi código?
No. El intérprete almacena las anotaciones en __annotations__ y, por lo demás, las ignora; el comportamiento en tiempo de ejecución es idéntico con o sin sugerencias.
¿Quién lee realmente las sugerencias de tipo, entonces?
Un verificador de tipos estático separado (mypy o pyright son los dos dominantes) lee tu código fuente sin ejecutarlo e informa inconsistencias con las tipos que declaraste o que infirió.
¿Qué significa "tipificación gradual" en la práctica?
Significa que el código tipificado y no tipificado pueden coexistir en la misma base de código. El código sin anotar se trata como Any implícito, por lo que un verificador aún puede analizar las partes que sí tienen sugerencias sin exigir que todo el programa esté tipificado a la vez.
¿Cuál es la diferencia entre tipificación estructural y nominal?
La tipificación nominal verifica la ascendencia de la clase: un valor coincide con un tipo si su clase es ese tipo o una subclase. La tipificación estructural (a través de Protocol) verifica la forma en su lugar: un valor coincide si tiene los métodos y atributos correctos, independientemente de la herencia.
¿Por qué `Any` se considera arriesgado si no causa errores?
Porque es contagioso. Cada expresión derivada de un valor Any también es implícitamente Any, por lo que la verificación se desactiva silenciosamente para toda una cadena posterior a partir de un solo valor sin anotar o explícitamente Any.
¿Puede un verificador de tipos detectar un error que solo aparece con ciertos datos de entrada?
No directamente. Un verificador razona a partir de tipos declarados e inferidos, no de valores reales en tiempo de ejecución, por lo que no puede detectar un error causado por, por ejemplo, JSON malformado de una API; eso requiere validación en tiempo de ejecución, no análisis estático.
¿Necesito Pydantic si ya uso sugerencias de tipo en todas partes?
Para valores internos, ya tipificados, las sugerencias más un verificador suelen ser suficientes. Para datos que entran al programa desde el exterior (cuerpos HTTP, archivos de configuración, mensajes de cola), necesitas algo que valide en tiempo de ejecución, ya que el verificador nunca ve esos datos antes de que se ejecuten.
¿Por qué `list[int]` importa si `list` funciona bien en tiempo de ejecución?
En tiempo de ejecución no hay diferencia; ambas son la misma clase list. Estáticamente, sin embargo, list[int] le dice al verificador qué hay dentro, por lo que puede detectar código que intenta añadir una cadena o iterar asumiendo el tipo de elemento incorrecto.
¿Adoptar el modo estricto es una decisión de todo o nada?
No. Las configuraciones de estrictez como disallow_untyped_defs o warn_return_any se pueden habilitar por módulo en pyproject.toml, que es cómo migran la mayoría de las bases de código: ajustando un paquete a la vez en lugar de activar un único interruptor global.
¿Por qué Python se molesta con dos filosofías de tipificación separadas (Protocol vs clases) en lugar de una?
Porque el código real necesita ambas. Tu propio modelo de dominio a menudo tiene relaciones de herencia genuinas que vale la pena expresar nominalmente, mientras que los objetos de terceros o duck-typed necesitan ser describibles solo por su forma; Protocol existe específicamente para cubrir ese segundo caso sin forzar la herencia en código que no posees.
¿Significa que mi código está libre de errores si un verificador de tipos pasa?
No, solo demuestra que el código es internamente consistente con las tipos que declaraste o que el verificador infirió. Errores lógicos, desajustes de datos en tiempo de ejecución y cualquier cosa fuera del vocabulario del sistema de tipos aún pueden colarse.
¿Por qué Python añadió la sintaxis `X | Y` en lugar de mantener `Union[X, Y]`?
Es más corta y se lee más cerca de cómo las personas ya describen los tipos en prosa, y refleja una tendencia más amplia de cerrar la brecha entre lo que el sistema de tipos puede expresar y cómo se escribe realmente el Python idiomático.
Relacionados
- Conceptos Básicos de Sugerencias de Tipo - sintaxis y ejemplos prácticos de anotación
- Protocolos y Tipificación Estructural - tipificación basada en forma en profundidad
- Genéricos y TypeVar - tipos paramétricos y sintaxis PEP 695
- Estrategia de Tipificación Gradual - adopción de tipos en una base de código existente
- Configuración de mypy y pyright - configuración de los verificadores que esta página describe
- Mejores Prácticas de Sugerencias de Tipo - dónde las tipos rinden frutos en la práctica
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.