Después de mucho tiempo trabajando con Typescript el cambio a Python fue duro, muy duro sobre todo en lo que a los tipos se refiere.
¿Qué es MyPy?
Python es un lenguaje súper divertido y esta diversión se debe principalmente a su dinamismo, pero a su vez este dinamismo (al menos para mi parecer) tiene un gran problema y es que existirán probabilidades muy altas de que se produzcan errores en tiempo de ejecución.
Aquí entra MyPy en acción, una herramienta que nos va a permitir validar de forma estática los tipos de nuestros programas escritos en Python.
Cómo empezar a usar MyPy
Tanto para usar MyPy como un comando, como para añadirlo como dependencia dentro de nuestro proyecto es súper sencillo. Bastaría con ejecutar uno de los siguientes comandos:
pip3 install mypy # global
pip3 install -r requirements.txt # dependencia a nivel de proyecto
poetry add mypy # dependencia a nivel de proyecto usando poetry
Usar MyPy es tan sencillo como ejecutar:
mypy .
para validar todos los ficheros de nuestro proyecto.mypy my_directory
para validar todos los fichero python dentro de un fichero.mypy my_file.py
para validar un fichero python en concreto.
Configurar MyPy
MyPy se puede configurar fácilmente mediante un fichero de configuración llamada
mypy.ini
. Este fichero tiene una serie de características que paso a
explicar:
- Una sección llamada
[mypy]
para especificar reglas a nivel general. - Pueden existir otras secciones con la forma
[mypy-PATTERN1,PATTERN2,...]
dondePATTERN1
,… hacen referencia a los módulos del proyecto. - Podemos deshabilitar los tipos en una línea en concreto con
# type: ignore
Este podría ser un ejemplo típico de un fichero de configuración:
# Global options:
[mypy]
python_version = 2.7
warn_return_any = True
warn_unused_configs = True
# Per-module options:
[mypy-mycode.foo.*]
disallow_untyped_defs = True
[mypy-mycode.bar]
warn_return_any = False
[mypy-somelibrary]
ignore_missing_imports = True
Los principales tipos que puedes usar en MyPy
Dentro del lenguaje de Python existen una serie de tipos que podremos usar para especificar a MyPy y que este nos valide si los estamos usando correctamente.
- Tipos primitivos:
- str
- int
- float
- bool
- bytes
- Tipos complejos:
- Any
- None
- List
- Dict
- Set
- Tuple
- Optional
- Callable
- Iterator
- Union
Ejemplos de uso de MyPy
Caso sencillo
Después de tanta teoría vamos a practicar un poco! Imaginemos que tenemos el siguiente método en nuestro código:
def add_two_numbers(x, y):
return x + y
¿Qué ocurriría si le pasamos como parámetros algo que no son números?
Pues en este caso en concreto, nada, pero no estaríamos usando correctamente nuestro método add_two_numbers
ya que
concatenariamos dos cadenas de texto en lugar de sumar dos números.
Esto se puede solucionar de forma muy sencilla añadiendo tipos:
def add_two_numbers(x: int, y: int) -> int:
return x + y
De esta forma, si usamos MyPy seríamos capaces de detectar este tipo de errores de forma inmediata:
add_two_numbers(1, 2) # Todo ok!
add_two_numbers("a", "b") # Argument 1 to "add_two_numbers" has incompatible type "str"; expected "int"
Caso complejo
El caso anterior es muy sencillo, así que vamos a ver otro más complejo y seguro que súper habitual en nuestro día a día.
Imaginemos que tenemos una clase que se llama Person
y otra Alien
:
class Person:
def __init__(self, name):
self.name = name
def speak(self):
return f"Hi {self.name}"
class Alien:
def __init__(self, name):
self.name = name
def yell(self):
return f"Hi {self.name}"
Finalmente tenemos un método que recibe una lista de personas y envía un saludo a cada una de ellas:
def say_hey_all(people):
for person in people:
person.speak()
¿Que ocurriría si por error le pasaramos a nuestro método say_hey_all
una lista
de aliens
?
Pues que durante el desarrollo no encontraríamos ningún error, pero en
tiempo de ejecución se producirá un error y se lanzaría una excepción ya que
la clase Alien
no tiene el método speak
.
Para evitar este tipo de errores MyPy nos soluciona la vida ya que nos los va a detectar de forma inmediata. Bastaría con añadir los tipos a nuestro método y ejecutar MyPy:
def say_hey_all(people: List[Person]) -> None:
for person in people:
person.speak()
people = [Person("Peter"), Person("Brian"), Person("Stewie")]
aliens = [Alien("Alf"), Alien("ET"), Alien("Thanos")]
say_hey_all(people) # Todo ok
say_hey_all(aliens) # Argument 1 to "say_hey_all" has incompatible type "List[Alien]"; expected "List[Person]"
Conclusiones
Como hemos podido ver en este breve post, utilizar MyPy puede ser muy útil si quieres tener una red de seguridad extra en lo que a los tipos se refiere y dar un poco más de cuerpo a un lenguaje tan dinámico como Python.
¡Espero que os haya gustado!