Python, a pesar de ser un lenguaje de tipado dinámico, permite el uso de anotaciones de tipos para indicar los tipos de datos esperados en variables, argumentos de funciones y valores de retorno. Aunque no son obligatorias, estas anotaciones contribuyen a mejorar la legibilidad y el mantenimiento del código, además de facilitar la detección de errores potenciales.
Las anotaciones de tipos mejoran la experiencia de desarrollo al ofrecer sugerencias más precisas en los editores de código. Además, algunas librerías como Pydantic aprovechan estas anotaciones para proporcionar características avanzadas, como la validación automática de datos.
Anotación en variables
Puedes anotar las variables simples con su tipo utilizando : seguido del tipo de dato. Los tipos pueden ser cualquiera de los básicos de Python, concretamente str, int, float y bool. Aunque no es común anotar variables, en algunos casos puede ser útil para aclarar el comportamiento del código.
nombre: str = "Juan"
edad: int = 30Anotación en funciones
Puedes realizar las anotaciones en los parámetros de las funciones de la misma manera que con las variables. Para el valor de retorno utiliza -> seguido del tipo. Es común ver la anotación -> None, que indica que la función no tiene valor de retorno (por ejemplo, el método __init__() de una clase).
def envia_saludo(nombre: str) -> str:
return f"Hola, {nombre}"
def imprime_saludo(nombre: str) -> None:
print(f"Hola, {nombre}")Tipos compuestos
Para anotar tipos en colecciones como listas, diccionarios o tuplas, utiliza [] para indicar el tipo de los elementos que contiene. En el caso de los diccionarios, debes especificar los tipos tanto de las claves como de los valores, mientras que para las tuplas debes anotar los tipos de cada uno de sus elementos.
def suma_numeros(numeros: list[int]) -> int:
return sum(numeros)
def cuenta_ocurrencias(palabras: list[str]) -> dict[str, int]:
resultado = {}
for palabra in palabras:
resultado[palabra] = resultado.get(palabra, 0) + 1
return resultadoUniones y opcionales
Puedes utilizar el operador | para indicar una unión de tipos, lo que permite especificar que una variable o parámetro puede aceptar múltiples tipos. Por ejemplo, si un parámetro puede ser tanto int como float, puedes indicarlo así:
def cubo(numero: int | float) -> int | float:
return numero**3También puedes usar None en combinación con otro tipo para señalar que el valor es opcional. Esto puedes lograrlo con el operador |:
def buscar_elemento(lista: list[int], elemento: int) -> int | None:
if elemento in lista:
return lista.index(elemento)
return NoneO bien, utilizando Optional del módulo typing:
from typing import Optional
def buscar_elemento(lista: list[int], elemento: int) -> Optional[int]:
if elemento in lista:
return lista.index(elemento)
return NoneUso de clases en anotaciones de tipos
Además de los tipos primitivos, también puedes utilizar clases personalizadas en las anotaciones de tipos. Esto es especialmente útil cuando una función espera recibir o retornar una instancia de una clase específica. Esto facilita la comprensión del código, permite una mejor autocompletación en los entornos de desarrollo y mejora la legibilidad.
En el siguiente ejemplo, la función imprime_informacion() toma como argumento un objeto de la clase Persona:
class Persona:
def __init__(self, nombre: str, edad: int):
self.nombre = nombre
self.edad = edad
def saludar(self) -> str:
return f"Hola, mi nombre es {self.nombre} y tengo {self.edad} años."
def imprime_informacion(persona: Persona) -> None:
print(persona.saludar())Aquí, la anotación de tipo Persona indica claramente que el parámetro persona debe ser una instancia de la clase Persona, mejorando la claridad y proporcionando una verificación estática de tipos con herramientas como mypy.
Clases en colecciones
Es posible combinar clases con colecciones, al igual que con tipos primitivos. Por ejemplo, si tienes una función que devuelve una lista de objetos de la clase Persona, puedes anotarla de la siguiente manera:
def carga_personas() -> list[Persona]:
datos = []
# carga de datos...
return datosAquí, la anotación list[Persona] indica que la función retorna una lista que contiene instancias de la clase Persona.
Anotaciones adelantadas
En algunos casos, puede que necesites anotar una clase que aún no ha sido definida, como cuando dos clases dependen mutuamente en sus anotaciones. Para estos casos, puedes usar comillas para indicar el nombre de la clase, retrasando la evaluación de la anotación hasta que la clase sea definida. Esto es conocido como una anotación adelantada:
class A:
def __init__(self, b: list["B"]):
self.b = b
class B:
def __init__(self, a: "A"):
self.a = aEn este ejemplo, las clases A y B hacen referencia a sí mismas de manera recíproca, lo que se logra usando comillas alrededor de los nombres de las clases en las anotaciones de tipo.
Validación de anotaciones de tipos
Python no realiza validaciones automáticas de los tipos anotados. Sin embargo, los editores de código como VS Code o PyCharm usan esta información para ayudar a detectar errores en tiempo de desarrollo.
Además, existen herramientas como mypy o pyright que permiten verificar que el código cumple con las anotaciones de tipos.
Por ejemplo, dado el siguiente código:
def suma(a: int, b: int) -> int:
return a + b
resultado = suma(10, 12.5) # float en lugar de un intAl ejecutar mypy con este código, se generará el siguiente error:
main.py:4: error: Argument 2 to "suma" has incompatible type "float"; expected "int" [arg-type]
Found 1 error in 1 file (checked 1 source file)No obstante, si se ejecuta este código directamente con Python, no se lanzará ningún error.