{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# MyPy - Python y un sistema de tipado estático" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*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.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "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). \n", "\n", "Por ejemplo, en un código tan trivial como el siguiente:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def saludo(nombre):\n", " return 'Hola {}'.format(nombre)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hola Raul\n", "Hola 1\n" ] } ], "source": [ "print (saludo('Raul'))\n", "print (saludo(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hola Raul\n", "Error: el argumento debe ser del tipo String(str)\n" ] } ], "source": [ "def saludo(nombre):\n", " if type(nombre) != str:\n", " return \"Error: el argumento debe ser del tipo String(str)\"\n", " return 'Hola {}'.format(nombre)\n", "\n", "print(saludo('Raul'))\n", "print(saludo(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## MyPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[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.\n", "\n", "Algunos de los beneficios que proporciona utilizar [MyPy](http://www.mypy-lang.org/) son:\n", "\n", "* **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). \n", "* **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. \n", "* **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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tipos de datos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Estos son algunos de los tipos de datos más comunes que podemos encontrar en [Python](http://python.org/):\n", "\n", "* **`int`**: Número entero de tamaño arbitrario\n", "* **`float`**: Número flotante.\n", "* **`bool`**: Valor booleano (True o False)\n", "* **`str`**: Unicode string\n", "* **`bytes`**: 8-bit string\n", "* **`object`**: Clase base del que derivan todos los objecto en [Python](http://python.org/).\n", "* **`List[str]`**: lista de objetos del tipo string.\n", "* **`Dict[str, int]`**: Diccionario de string hacia enteros\n", "* **`Iterable[int]`**: Objeto iterable que contiene solo enteros.\n", "* **`Sequence[bool]`**: Secuencia de valores booleanos\n", "* **`Any`**: Admite cualquier valor. (tipado dinámico)\n", "\n", "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/).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ejemplos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting typeTest.py\n" ] } ], "source": [ "%%writefile typeTest.py\n", "import typing\n", "\n", "def saludo(nombre: str) -> str:\n", " return 'Hola {}'.format(nombre)\n", "\n", "print(saludo('Raul'))\n", "print(saludo(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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*." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "typeTest.py, line 7: Argument 1 to \"saludo\" has incompatible type \"int\"; expected \"str\"\r\n" ] } ], "source": [ "!mypy typeTest.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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!" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hola Raul\r\n", "Hola 1\r\n" ] } ], "source": [ "!python3 typeTest.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Tipado explicito para variables y colecciones" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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/)*. \n", "\n", "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/)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting typeTest.py\n" ] } ], "source": [ "%%writefile typeTest.py\n", "from typing import Undefined, List, Dict\n", "\n", "# Declaro los tipos de las variables\n", "texto = Undefined(str)\n", "entero = Undefined(int)\n", "lista_enteros = List[int]()\n", "dic_str_int = Dict[str, int]()\n", "\n", "# Asigno valores a las variables.\n", "texto = 'Raul'\n", "entero = 13\n", "lista_enteros = [1, 2, 3, 4]\n", "dic_str_int = {'raul': 1, 'ezequiel': 2}\n", "\n", "# Intento asignar valores de otro tipo.\n", "texto = 1\n", "entero = 'raul'\n", "lista_enteros = ['raul', 1, '2']\n", "dic_str_int = {1: 'raul'}\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "typeTest.py, line 16: Incompatible types in assignment (expression has type \"int\", variable has type \"str\")\r\n", "typeTest.py, line 17: Incompatible types in assignment (expression has type \"str\", variable has type \"int\")\r\n", "typeTest.py, line 18: List item 1 has incompatible type \"str\"\r\n", "typeTest.py, line 18: List item 3 has incompatible type \"str\"\r\n", "typeTest.py, line 19: List item 1 has incompatible type \"Tuple[int, str]\"\r\n" ] } ], "source": [ "!mypy typeTest.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting typeTest.py\n" ] } ], "source": [ "%%writefile typeTest.py\n", "from typing import List, Dict\n", "\n", "# Declaro los tipos de las variables\n", "texto = '' # type: str\n", "entero = 0 # type: int\n", "lista_enteros = [] # type: List[int]\n", "dic_str_int = {} # type: Dict[str, int]\n", "\n", "# Asigno valores a las variables.\n", "texto = 'Raul'\n", "entero = 13\n", "lista_enteros = [1, 2, 3, 4]\n", "dic_str_int = {'raul': 1, 'ezequiel': 2}\n", "\n", "# Intento asignar valores de otro tipo.\n", "texto = 1\n", "entero = 'raul'\n", "lista_enteros = ['raul', 1, '2']\n", "dic_str_int = {1: 'raul'}" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "typeTest.py, line 16: Incompatible types in assignment (expression has type \"int\", variable has type \"str\")\r\n", "typeTest.py, line 17: Incompatible types in assignment (expression has type \"str\", variable has type \"int\")\r\n", "typeTest.py, line 18: List item 1 has incompatible type \"str\"\r\n", "typeTest.py, line 18: List item 3 has incompatible type \"str\"\r\n", "typeTest.py, line 19: List item 1 has incompatible type \"Tuple[int, str]\"\r\n" ] } ], "source": [ "!mypy typeTest.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Instalando MyPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instalar [MyPy](http://www.mypy-lang.org/) es bastante fácil, simplemente debemos seguir los siguientes pasos:\n", "\n", "Si utilizan [git](https://github.com/), pueden clonar el repositorio de mypy:\n", "\n", "**$ git clone https://github.com/JukkaL/mypy.git **\n", "\n", "Si no utilizan [git](https://github.com/), como alternativa, se pueden descargar la última versión de mypy en el siguiente link:\n", "\n", "https://github.com/JukkaL/mypy/archive/master.zip\n", "\n", "\n", "Una vez que ya se lo descargaron, se posicionan dentro de la carpeta de mypy y ejecutan el script `setup.py` para instalarlo:\n", "\n", "**$ python3 setup.py install **\n", "\n", "Reemplacen 'python3' con su interprete para python3." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### MyPy como parte de Python 3.5 ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:\n", "\n", "https://mail.python.org/pipermail/python-ideas/2014-August/028618.html\n", "\n", "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/)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Saludos!\n", "\n", "*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).*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3+" } }, "nbformat": 4, "nbformat_minor": 0 }