#!/usr/bin/env python # coding: utf-8 # # MyPy - Python y un sistema de tipado estático # *Esta notebook fue creada originalmente como un blog post por [Raúl E. López Briega](http://relopezbriega.com.ar/) en [Mi blog sobre Python](http://relopezbriega.github.io). El contenido esta bajo la licencia BSD.* # Una de las razones por la que solemos amar a [Python](http://python.org/), es por su sistema de [tipado dinámico](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_din.C3.A1mico), el cual lo convierte en un lenguaje de programación sumamente flexible y fácil de aprender; al no tener que preocuparnos por definir los tipos de los objetos, ya que [Python](http://python.org/) los infiere por nosotros, podemos escribir programas en una forma mucho más productiva, sin verbosidad y utilizando menos líneas de código. # # Ahora bien, este sistema de [tipado dinámico](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_din.C3.A1mico) también puede convertirse en una pesadilla en proyectos de gran escala, requiriendo varias horas de [pruebas unitarias](http://es.wikipedia.org/wiki/Unit_testing) para evitar que los objetos adquieran un tipo de datos que no deberían y complicando el su mantenimiento o futura [refactorización](http://es.wikipedia.org/wiki/Refactorizaci%C3%B3n). # # Por ejemplo, en un código tan trivial como el siguiente: # In[1]: def saludo(nombre): return 'Hola {}'.format(nombre) # Esta simple función nos va a devolver el texto 'Hola' seguido del nombre que le ingresemos; pero como no contiene ningún control sobre el tipo de datos que pude admitir la variable *nombre*, los siguientes casos serían igualmente válidos: # In[2]: print (saludo('Raul')) print (saludo(1)) # En cambio, si pusiéramos un control sobre el tipo de datos que admitiera la variable *nombre*, para que siempre fuera un *string*, entonces el segundo caso ya no sería válido y lo podríamos detectar fácilmente antes de que nuestro programa se llegara a ejecutar. # # Obviamente, para poder detectar el segundo error y que nuestra función *saludo* solo admita una variable del tipo *string* como argumento, podríamos reescribir nuestra función, agregando un control del tipo de datos de la siguiente manera: # In[3]: def saludo(nombre): if type(nombre) != str: return "Error: el argumento debe ser del tipo String(str)" return 'Hola {}'.format(nombre) print(saludo('Raul')) print(saludo(1)) # Pero una solución más sencilla a tener que ir escribiendo condiciones para controlar los tipos de las variables o de las funciones es utilizar [MyPy](http://www.mypy-lang.org/) # ## MyPy # [MyPy](http://www.mypy-lang.org/) es un proyecto que busca combinar los beneficios de un sistema de [tipado dinámico](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_din.C3.A1mico) con los de uno de [tipado estático](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_est.C3.A1tico). Su meta es tener el poder y la expresividad de [Python](http://python.org/) combinada con los beneficios que otorga el chequeo de los *tipos* de datos al momento de la compilación. # # Algunos de los beneficios que proporciona utilizar [MyPy](http://www.mypy-lang.org/) son: # # * **Chequeo de tipos al momento de la compilación:** Un sistema de [tipado estático](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_est.C3.A1tico) hace más fácil detectar errores y con menos esfuerzo de [debugging](http://es.wikipedia.org/wiki/Debug). # * **Facilita el mantenimiento:** Las declaraciones explícitas de tipos actúan como documentación, haciendo que nuestro código sea más fácil de entender y de modificar sin introducir nuevos errores. # * **Permite crecer nuestro programa desde un tipado dinámico hacia uno estático:** Nos permite comenzar desarrollando nuestros programas con un [tipado dinámico](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_din.C3.A1mico) y a mediada que el mismo vaya madurando podríamos modificarlo hacia un [tipado estático](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_est.C3.A1tico) de forma muy sencilla. De esta manera, podríamos beneficiarnos no solo de la comodidad de [tipado dinámico](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_din.C3.A1mico) en el desarrollo inicial, sino también aprovecharnos de los beneficios de los [tipos estáticos](http://es.wikipedia.org/wiki/Sistema_de_tipos#Tipado_est.C3.A1tico) cuando el código crece en tamaño y complejidad. # ### Tipos de datos # Estos son algunos de los tipos de datos más comunes que podemos encontrar en [Python](http://python.org/): # # * **`int`**: Número entero de tamaño arbitrario # * **`float`**: Número flotante. # * **`bool`**: Valor booleano (True o False) # * **`str`**: Unicode string # * **`bytes`**: 8-bit string # * **`object`**: Clase base del que derivan todos los objecto en [Python](http://python.org/). # * **`List[str]`**: lista de objetos del tipo string. # * **`Dict[str, int]`**: Diccionario de string hacia enteros # * **`Iterable[int]`**: Objeto iterable que contiene solo enteros. # * **`Sequence[bool]`**: Secuencia de valores booleanos # * **`Any`**: Admite cualquier valor. (tipado dinámico) # # El tipo **Any** y los constructores **List, Dict, Iterable y Sequence** están definidos en el modulo **typing** que viene junto con [MyPy](http://www.mypy-lang.org/). # # ### Ejemplos # Por ejemplo, volviendo al ejemplo del comienzo, podríamos reescribir la función *saludo* utilizando [MyPy](http://www.mypy-lang.org/) de forma tal que los tipos de datos sean explícitos y puedan ser chequeados al momento de la compilación. # In[4]: get_ipython().run_cell_magic('writefile', 'typeTest.py', "import typing\n\ndef saludo(nombre: str) -> str:\n return 'Hola {}'.format(nombre)\n\nprint(saludo('Raul'))\nprint(saludo(1))\n") # En este ejemplo estoy creando un pequeño script y guardando en un archivo con el nombre 'typeTest.py', en la primer línea del script estoy importando la librería *typing* que viene con [MyPy](http://www.mypy-lang.org/) y es la que nos agrega la funcionalidad del chequeo de los tipos de datos. Luego simplemente ejecutamos este script utilizando el interprete de [MyPy](http://www.mypy-lang.org/) y podemos ver que nos va a detectar el error de tipo de datos en la segunda llamada a la función *saludo*. # In[5]: get_ipython().system('mypy typeTest.py') # Si ejecutáramos este mismo script utilizando el interprete de [Python](http://python.org/), veremos que obtendremos los mismos resultados que al comienzo de este *notebook*; lo que quiere decir, que la sintaxis que utilizamos al reescribir nuestra función *saludo* es código [Python](http://python.org/) perfectamente válido! # In[6]: get_ipython().system('python3 typeTest.py') # #### Tipado explicito para variables y colecciones # En el ejemplo anterior, vimos como es la sintaxis para asignarle un *tipo de datos* a una función, la cual utiliza la sintaxis de [Python3](http://python.org/), *[annotations](http://legacy.python.org/dev/peps/pep-3107/)*. # # Si quisiéramos asignarle un *tipo* a una variable, podríamos utilizar la función **`Undefined`** que viene junto con [MyPy](http://www.mypy-lang.org/). # In[7]: get_ipython().run_cell_magic('writefile', 'typeTest.py', "from typing import Undefined, List, Dict\n\n# Declaro los tipos de las variables\ntexto = Undefined(str)\nentero = Undefined(int)\nlista_enteros = List[int]()\ndic_str_int = Dict[str, int]()\n\n# Asigno valores a las variables.\ntexto = 'Raul'\nentero = 13\nlista_enteros = [1, 2, 3, 4]\ndic_str_int = {'raul': 1, 'ezequiel': 2}\n\n# Intento asignar valores de otro tipo.\ntexto = 1\nentero = 'raul'\nlista_enteros = ['raul', 1, '2']\ndic_str_int = {1: 'raul'}\n") # In[8]: get_ipython().system('mypy typeTest.py') # Otra alternativa que nos ofrece [MyPy](http://www.mypy-lang.org/) para asignar un *tipo de datos* a las variables, es utilizar comentarios; así, el ejemplo anterior lo podríamos reescribir de la siguiente forma, obteniendo el mismo resultado: # In[9]: get_ipython().run_cell_magic('writefile', 'typeTest.py', "from typing import List, Dict\n\n# Declaro los tipos de las variables\ntexto = '' # type: str\nentero = 0 # type: int\nlista_enteros = [] # type: List[int]\ndic_str_int = {} # type: Dict[str, int]\n\n# Asigno valores a las variables.\ntexto = 'Raul'\nentero = 13\nlista_enteros = [1, 2, 3, 4]\ndic_str_int = {'raul': 1, 'ezequiel': 2}\n\n# Intento asignar valores de otro tipo.\ntexto = 1\nentero = 'raul'\nlista_enteros = ['raul', 1, '2']\ndic_str_int = {1: 'raul'}\n") # In[10]: get_ipython().system('mypy typeTest.py') # ### Instalando MyPy # Instalar [MyPy](http://www.mypy-lang.org/) es bastante fácil, simplemente debemos seguir los siguientes pasos: # # Si utilizan [git](https://github.com/), pueden clonar el repositorio de mypy: # # **$ git clone https://github.com/JukkaL/mypy.git ** # # Si no utilizan [git](https://github.com/), como alternativa, se pueden descargar la última versión de mypy en el siguiente link: # # https://github.com/JukkaL/mypy/archive/master.zip # # # Una vez que ya se lo descargaron, se posicionan dentro de la carpeta de mypy y ejecutan el script `setup.py` para instalarlo: # # **$ python3 setup.py install ** # # Reemplacen 'python3' con su interprete para python3. # ### MyPy como parte de Python 3.5 ? # Guido van Rossum, el creador de [Python](http://python.org/), ha enviado reciente una propuesta a la lista de correo de python-ideas, en la cual sugiere agregar en la próxima versión de [Python](http://python.org/) la sintaxis de [MyPy](http://www.mypy-lang.org/) para las [functions annotations](http://legacy.python.org/dev/peps/pep-3107/). Pueden encontrar la propuesta en el siguiente link: # # https://mail.python.org/pipermail/python-ideas/2014-August/028618.html # # También pueden seguir las discusiones que se generaron sobre este tema en [Reddit](http://www.reddit.com/r/programming/comments/2disob/proposal_for_python_type_annotations_from_guido/) # # # Saludos! # # *Este post fue escrito utilizando IPython notebook. Pueden descargar este [notebook](https://github.com/relopezbriega/relopezbriega.github.io/blob/master/downloads/MyPy-Python-Tipado-estatico.ipynb) o ver su version estática en [nbviewer](http://nbviewer.ipython.org/github/relopezbriega/relopezbriega.github.io/blob/master/downloads/MyPy-Python-Tipado-estatico.ipynb).*