Introducción a anotación y tipos en Python
Posted on sáb 22 enero 2022 in Tutorial Python • 4 min read
Artículos anteriores se tocó el tema de uso de dataclass, pydantic y orjson y como usar el tipado, en este artículo se explicará las mejores prácticas:
La verificación de tipos aparte de ayudar a entender mejor el código facilita la detección de errores rapidamente y automáticamente usando por ejemplo mypy. La verificación de tipos se incorporó en Python desde la versión 3.5.
Este artículo se basa en el artículo en inglés: Improving Your Python Projects With Type Hints
Sintaxis de tipado
Python soporta anotaciones de tipos para variables básicas soportadas por el lenguaje str, int, float, bool y None. Viene incorporada en la librería typing.
Anotación de variables
Se puede indicar el tipo de una variable al usar los dos puntos ":". Por ejemplo:
from typing import List
nombre: str = 'Pedro'
edad: int = 24
altura_mts: float = 1.7
colegas: List[str] = ['Jane', 'John']
Se define el tipo y el valor que tendrá esa variable.
Anotación en las funciones
En el caso de las funciones se puede colocar anotaciones en las variables de los argumentos, así como en la salida de la función.
from typing import Dict, List
import math
def calc_area_circulo(radio: float) -> float:
return math.pi*(radio**2)
def send_email(
subject: str,
body: str,
recipients: List[str],
cache: Dict[str, str]
) -> bool:
# ....
# Devuelve True o False el envío de un correo
return True
En la primera función se pasa como argumento el radio de tipo flotante y devuelve el resultado del área del circulo que es flotante también.
EN el siguiente ejemplo se define una función que recibe subject (string), body (string),recipients (lista de string), cache (diccionario que tiene llaves de string y valores de string) y la función devuelve boleano.
Usar tipos especiales
Existe una serie de tipos que complementan a los básicos:
- Any:
- Literal:
- Union
- TypeDict
- NoReturn
- Final
Any
Una variable tipo Any es compatible con todas los tipos básicos.
from typing import Any
resultado: Any = "Procesado"
resultado = 10
estatus: str = "Pendiente"
estatus = resultado
En el primer caso se define la variable resultado tipo Any que se le asigna un string, luego se le asigna un entero, el cual no es problema por que el tipo Any lo soporta. Luego se define la variable estatus de tipo string y asignando un string, luego sin problemas se asigna resultado a la variable estatus; esto tampoco es problema por que resultado es tipo any.
Literal
El tipo literal se usa para indicar que tiene un valor igual a los valores indicados (tipos).
from typing import Literal
GENERO = Literal["hombre", "mujer", "no especificado"]
def crear_usuario(
nombre: str,
apellido: str,
genero: GENERO,
) -> Dict[str, str]:
return {
"nombre": nombre,
"apellido": apellido,
"genero": genero
}
crear_usuario("John", "Doe", "hombre")
{'nombre': 'John', 'apellido': 'Doe', 'genero': 'hombre'}
Se define una lista para el genero de tipo literal, y el tipo es uno de los definidos (hombre, mujer, no especificado).
Union
Algunas variables a veces necesitan dos tipos básicos.
from typing import Union
def get_temperatura() -> Union[int, float]:
return 20.8 # funciona también con 20.
Se define una función que devuelve entero o flotante, esto se logra usando Union.
TypedDict
TypedDict permite definir un tipo de dato diccionario.
from typing import TypedDict, Union
class Card(TypedDict):
rank: Union[str, int]
suit: str
# The Card class now has behaviours of both TypedDict and dict classes
# Card can be used to annotate a variable
ace_of_spade: Card = {'rank': 'A', 'suit': '♤'}
# or can be instantiated
ace_of_spade = Card(rank='A', suit='♤')
print(ace_of_spade)
{'rank': 'A', 'suit': '♤'}
NoReturn
NoReturn es similar to void en otros lenguajes de programación, en mi caso antes usaba None cuando una función no devuelve nada.
from typing import NoReturn
def hola1() -> None:
print("Hola Mundo!")
def hola2() -> NoReturn:
print("Hola Mundo!")
Se definen dos funciones hola, la primera como None y la segunda como NoReturn, que es como debe definirse una función que no devuelve un valor.
Final
Este tipo de dato se define para no redefinir un valor.
from typing import Final
MIN_NOMBRE_LONG: Final = 2
# mypy reporta error por que se le está asignando un nuevo valor a la
# variable
MIN_NOMBRE_LONG += 1
class Validador(object):
MIN_NOMBRE_LONG: Final[int] = 4
class UserValidador(Validador):
# El interprete marca una bandera en esta línea
MIN_NOMBRE_LONG = 3
Uso de verificadores de tipo
Uno de los más usados verificadores de tipos es mypy. Se puede instalar usando pip:
pip install mypy
Para usarlo simplemente se llama a mypy y se le pasa el archivo a verificar.
El código a validar se muestra a continuación:
#!/usr/bin/env python3
from typing import Literal, Dict
GENERO = Literal["hombre", "mujer", "no especificado"]
def crear_usuario(
nombre: str,
apellido: str,
genero: GENERO,
) -> Dict[str, str]:
return {
"nombre": nombre,
"apellido": apellido,
"genero": genero
}
crear_usuario("John", "Doe", "hombre")
crear_usuario("Jane", "Doe", "mujer")
crear_usuario("John", "Doe", "x")
Este código devolverá error al intentar crear un usuario con genero x.
mypy pruebas_typing.py
content/code/pruebas_typing.py:23: error: Argument 3 to "crear_usuario" has incompatible type "Literal['x']"; expected "Union[Literal['hombre'], Literal['mujer'], Literal['no especificado']]"
Found 1 error in 1 file (checked 1 source file)
Este es el mensaje de error que devuelve al intentar colocar un valor x en el genero.
La notación de tipo y la verificación nos facilita validar que una variable tenga un tipo de datos siempre y no cambie, a mí me paso hace poco que hice una función que devolvía una lista, tiempo después un desarrollador cambio la lista por un diccionario, y la función funcionaba, pero al pasarle mypy.
¡Haz tu donativo! Si te gustó el artículo puedes realizar un donativo con Bitcoin (BTC) usando la billetera digital de tu preferencia a la siguiente dirección: 17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV
O Escaneando el código QR desde la billetera: