Cuando escribes x = 42 en Python, algo distinto ocurre respecto a lo que pasa en Java o C. No estás declarando “una caja de tipo entero llamada x“. Estás creando un objeto entero con valor 42 en memoria, y luego pegando una etiqueta llamada x que apunta a él. La variable es la etiqueta; el tipo es propiedad del objeto.
Eso es el tipado dinámico en su esencia: el tipo está adjunto al objeto, no a la variable que lo referencia.
La consecuencia inmediata es que esto es perfectamente válido:
x = 42 x = "hola" x = [1, 2, 3]
x no “cambió de tipo”. Lo que cambió es a qué objeto apunta la etiqueta. El entero 42 sigue siendo un entero, la cadena "hola" sigue siendo una cadena — simplemente ya no hay ninguna variable apuntando al 42. Esto contrasta con el tipado estático, donde la variable tiene un tipo fijo declarado en tiempo de compilación y el compilador rechaza asignaciones incompatibles antes de ejecutar una sola línea.
Python elige este modelo por diseño deliberado: máxima flexibilidad y prototipado rápido, a cambio de que los errores de tipo solo aparezcan en tiempo de ejecución. No hay compilador que te avise que estás sumando un entero con una lista — te enterarás cuando ese código se ejecute. Ese es el costo real, ni más ni menos.
De esta propiedad surge directamente el duck typing: Python no pregunta “¿es este objeto de tipo X?” sino “¿tiene este objeto el método o atributo que necesito?”. Si camina como pato y grazna como pato, para Python es un pato. El tipo nominal no importa; importa la interfaz que expone el objeto.
Finalmente, los type hints (x: int = 42) no cambian nada de esto en tiempo de ejecución. Son anotaciones que Python ignora completamente al ejecutar el código. Su valor está en herramientas externas como mypy o los linters de los editores, que pueden leer esas anotaciones y avisarte de inconsistencias. Pero el intérprete sigue siendo dinámico — un hint no es una restricción.
# No se necesitan imports especiales para este ejemplo;
# el módulo 'typing' es solo para hints más complejos
def describir(valor):
# type(valor) interroga al OBJETO, no a ninguna declaración
print(f"El objeto {valor!r} es de tipo {type(valor).__name__}")
# La misma etiqueta apuntando a objetos de tipos distintos
x = 42
describir(x)
x = "hola"
describir(x)
x = [1, 2, 3]
describir(x)
# Duck typing en acción
class Pato:
def graznar(self):
return "Cuac"
class Persona:
def graznar(self):
return "Cuac (soy una persona, pero puedo)"
def hacer_graznar(cosa):
# No comprobamos isinstance(cosa, Pato) — no nos importa el tipo nominal
print(cosa.graznar())
hacer_graznar(Pato())
hacer_graznar(Persona()) # Funciona igual; tiene el método, eso basta
# Los type hints son invisibles en ejecución
def sumar(a: int, b: int) -> int:
return a + b
# Python no rechaza esto aunque viola los hints
resultado = sumar("hola", " mundo")
print(resultado) # "hola mundo" — sin error, sin advertencia del intérprete
Lo que revela cada parte del código
La función describir llama a type(valor) — y fíjate que el argumento es valor, el objeto en sí, no el nombre de la variable. Python consulta una tabla interna del objeto para saber su tipo. Cada objeto en Python lleva consigo metadatos: su tipo, su contador de referencias, su valor. Eso tiene un costo de memoria pequeño pero real: incluso un simple 42 es un objeto completo en el heap.
La sección de duck typing muestra algo que a veces desconcierta: hacer_graznar acepta tanto Pato como Persona sin herencia común, sin interfaz declarada, sin nada. Funciona porque en el momento en que se ejecuta cosa.graznar(), Python busca el método en el objeto que sea — si lo encuentra, adelante; si no, lanza AttributeError. La verificación ocurre en ese preciso instante de ejecución.
El ejemplo final con sumar es el más importante para internalizar el modelo. Definiste hints que dicen int, llamaste con str, y Python lo ejecutó sin rechistar. mypy habría gritado; el intérprete no. Los hints son metadatos para humanos y herramientas, no contratos que Python imponga.
Errores que debes conocer
Error: Asumir que isinstance es innecesario porque “Python es dinámico” y luego operar sobre un tipo incorrecto en silencio.
# ❌ Wrong
def incrementar(valor):
return valor + 1 # Explota con TypeError si alguien pasa un string
incrementar("5") # TypeError: can only concatenate str (not "int") to str
# ✅ Right
def incrementar(valor):
if not isinstance(valor, (int, float)):
raise TypeError(f"Se esperaba número, llegó {type(valor).__name__}")
return valor + 1
El error dinámico sigue siendo en ejecución, pero ahora el mensaje es útil y el fallo ocurre en el punto correcto, no tres llamadas más tarde.
Error: Confundir type hints con validación real y no agregar comprobaciones en código que recibe datos externos.
# ❌ Wrong
def procesar(datos: list) -> int:
return sum(datos) # Si datos viene de JSON y es None, explota
procesar(None) # TypeError: 'NoneType' object is not iterable
# ✅ Right
def procesar(datos: list) -> int:
if datos is None:
return 0
return sum(datos)
El hint list no filtra None en la frontera del sistema — datos externos siempre requieren validación explícita porque el intérprete nunca la hará por ti.
N° 19