summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/06_ejec_mod.md271
1 files changed, 271 insertions, 0 deletions
diff --git a/src/06_ejec_mod.md b/src/06_ejec_mod.md
new file mode 100644
index 0000000..0c54f4f
--- /dev/null
+++ b/src/06_ejec_mod.md
@@ -0,0 +1,271 @@
+# Módulos y ejecución
+
+Hasta ahora, has ejecutado el código en la REPL y de vez en cuando has usado
+`F5` en IDLE para ejecutar. Aunque te ha permitido salir del paso, necesitas
+saber más en detalle cómo funciona la ejecución para empezar a hacer tus
+programas. Además, es absurdo que te pelees contra todo, hay que saber qué
+batallas librar, así que necesitarás aprender a importar código realizado por
+otras personas para poder centrarte en lo que más te interesa: resolver tu
+problema.
+
+Este capítulo trata ambas cosas, que están muy relacionadas, y sirve como
+trampolín para el siguiente, la instalación de nuevos paquetes y la gestión de
+dependencias, y los posteriores sobre librerías interesantes que te facilitarán
+el desarrollo de tus proyectos.
+
+Este capítulo ya te capacita casi al cien por cien para la programación aunque
+aún no hemos trabajado su utilidad, pero seguro que alguna idea se te habrá
+ocurrido, si no no estarías leyendo este documento.
+
+## Terminología: módulos y paquetes
+
+En python a cualquier fichero de código (extensión `.py`) se le denomina
+*módulo* (*module*). A cualquier directorio que contenga módulos de código
+python y un fichero llamado `__init__.py`, que puede estar vacío, se le
+denomina *paquete* (*package*). El uso del fichero `__init__.py` permite que
+python busque módulos dentro del directorio.
+
+Piensa en los paquetes como grupos de módulos que incluso pueden anidarse con
+subpaquetes. En la documentación oficial verás en más de una ocasión que se
+trata a los paquetes como si fueran módulos, y tiene cierto sentido, porque,
+normalmente, existe un módulo principal que permite el acceso a un subpaquete.
+Así que, principalmente estás trabajando con un único módulo de un paquete, que
+contiene subpaquetes a los que este módulo hace referencia. Aunque pueda sonar
+algo enrevesado, no te preocupes por ahora: los módulos son ficheros únicos y
+los paquetes conjuntos de ellos.
+
+## Ejecución
+
+Ya conoces un par de maneras de ejecutar tus módulos de python. Usar la REPL,
+introduciéndole el código que quieres ejecutar, llamar a la función «ejecutar
+módulo» de IDLE con la tecla `F5` e incluso llamar a la shell de sistema con un
+comando similar a este:
+
+``` bash
+python mi_archivo.py
+```
+
+Normalmente, los ficheros de python se ejecutan de este último modo en
+producción, mientras que los dos anteriores son más usados a la hora de
+desarrollar. Realmente son métodos similares, en todos ellos el intérprete
+accede al fichero o contenido que recibe y ejecuta las líneas una a una.
+
+Existe también una opción adicional muy usada que sirve para ejecutar módulos
+que el sistema sea capaz de encontrar por sí mismo, en lugar de indicarle la
+ruta, se le puede indicar simplemente el nombre del módulo usando la opción
+`-m`:
+
+``` bash
+python -m nombre_de_modulo
+```
+
+## Importación y *namespaces*
+
+Anteriormente hemos pasado sobre la sintaxis de la importación de forma muy
+superficial pero tampoco es mucho más compleja a lo que ha aparecido. La
+sentencia `import` permite importar diferentes módulos a nuestro programa como
+en el siguiente ejemplo:
+
+``` python
+>>> import datetime
+>>> datetime
+<module 'datetime' from '/usr/lib/python3.6/datetime.py'>
+```
+
+Han pasado, sin embargo, muchas cosas interesantes en el ejemplo. En primer
+lugar, python ha buscado y encontrado el módulo `datetime` en el sistema y, en
+segundo lugar, ha creado un objeto módulo llamado `datetime` que atesora todas
+las definiciones globales del módulo `datetime`.
+
+Empezando por el final, python usa lo que se conoce como *namespace* de forma
+muy extendida. Los *namespaces*, de nombre (*name*) y espacio (*space*), son
+una herramienta para separar contextos ampliamente usada. Los objetos, en
+realidad, son una caso de *namespace* ya que cuando se llama a un método se le
+dice cuál es el contexto de la llamada, es decir: al método de qué objeto se
+llama.
+
+Para los módulos el proceso es el mismo. La sentencia `import` trae un módulo
+al programa pero lo esconde tras su *namespace*, de este modo, para acceder a
+algo definido en el recién importado módulo es necesario indicarle el nombre de
+éste de la siguiente manera:
+
+``` python
+>>> import datetime
+>>> datetime.date.today()
+datetime.date(2019, 12, 3)
+```
+
+En el ejemplo se accede a la clase `date` dentro del módulo `datetime`, y se
+lanza su función `today`, que indica el día de hoy. Como puedes apreciar, el
+operador `.` se utiliza del mismo modo que en las clases y objetos, y en
+realidad es difícil saber cuándo se está accediendo a una clase y cuándo a un
+módulo, aunque tampoco es necesario saberlo.
+
+Para no tener que escribir el nombre del módulo completo, existe otra versión
+de la sentencia `import` que tiene un comportamiento muy similar:
+
+``` python
+>>> import datetime as dt
+>>> dt.date.today()
+datetime.date(2019, 12, 3)
+```
+
+En este ejemplo, se ha cambiado el nombre del módulo a uno más corto decidido
+por el programador, `dt`. El funcionamiento es el mismo, simplemente se ha
+cambiado el nombre para simplificar. Este cambio de nombre también es útil
+cuando se va a importar un módulo cuyo nombre es igual que alguna otra
+definición. Cambiando el nombre se evitan colisiones.
+
+Existen versiones además, que permiten importar únicamente las funciones y
+clases seleccionadas, pero que las añaden al *namespace* actual, para evitar
+tener que usar el prefijo.
+
+``` python
+>>> from datetime import date
+>>> date.today()
+datetime.date(2019, 12, 3)
+```
+
+En este último ejemplo, se trae la clase `date` al contexto actual. También
+existe la posibilidad de importar más de una definición del módulo, usando la
+coma para separarlas, o todo lo que el módulo exponga mediante el símbolo `*`.
+Es peligroso, sin embargo, traer definiciones al namespace actual de forma
+descuidada, sobre todo con la última opción, porque, es posible que se repitan
+nombres por accidente y se pisen definiciones. Los namespaces se inventan con
+el fin de separar las definiciones y evitar colisiones de este tipo.
+
+### Búsqueda
+
+Una vez descrito cómo se interactúa con los módulos importados, es necesario
+describir dónde se buscan estos módulos.
+
+Los módulos se buscan en los siguientes lugares:
+
+1. El directorio del fichero ejecutado o el directorio de trabajo de la REPL
+2. Los directorios indicados en el entorno.
+3. La configuración por defecto (depende de la instalación)
+
+Esto significa que si guardas un archivo de python en IDLE y guardas otro más
+en el mismo directorio con el nombre `modulo.py` podrás importarlo usando
+`import modulo` en el primero, ya que comparten directorio. Lo mismo ocurre con
+los paquetes, crear un directorio con nombre `paquete` y añadirle un fichero
+vacío llamado `init.py` te permitirá hacer `import paquete`. Si añadieras más
+módulos dentro del paquete, podrías importar cada uno de ellos mediante
+`paquete.modulo`.
+
+> NOTA: Los nombres de los ficheros deben coincidir con el el nombre del módulo
+> más la extensión `.py`. En el caso de los directorios, saltar a un
+> subdirectorio implica acceder a un paquete, por lo que se añadirá un punto
+> (`.`).
+
+El primer punto sirve para facilitar que organices tu proyecto de python en
+varios módulos, separando así la funcionalidad en diferentes archivos.
+
+Los últimos dos puntos son los que permiten a python encontrar su librería
+estándar y los módulos de sistema. El tercero depende de la instalación y del
+formato de ésta: si python está instalado como portable no será igual que si se
+instala en el sistema del modo habitual. El segundo punto también puede variar
+de un sistema a otro, pero en resumen se trata de varias variables de entorno
+de sistema que le indican a python dónde buscar (normalmente toman el nombre
+`PYTHONPATH`, pero no es siempre así). El segundo punto puede alterarse de modo
+que en función de lo que se le indique, se puede pedir a python que busque los
+módulos en un lugar u otro.
+
+Estos lugares de búsqueda se pueden mostrar de la siguiente manera:
+
+``` python
+>>> import sys
+>>> print(sys.path)
+[ '',
+ '/usr/lib/python36.zip',
+ '/usr/lib/python3.6',
+ '/usr/lib/python3.6/lib-dynload',
+ '/usr/local/lib/python3.6/dist-packages',
+ '/usr/lib/python3/dist-packages']
+```
+
+En función del sistema en el que te encuentres y la configuración que tengas,
+python mostrará diferente resultado.
+
+Rescatando un ejemplo previo:
+
+``` python
+>>> import datetime
+>>> datetime
+<module 'datetime' from '/usr/lib/python3.6/datetime.py'>
+```
+
+Ahora entiendes por qué es capaz de encontrar `datetime` en
+`/usr/lib/python3.6`, carpeta listada en `sys.path`, bajo el nombre
+`datetime.py`.
+
+## Ejecución vs Importación: `__main__` *guard*
+
+A la hora de importar un módulo, python procesa el contenido de éste ya que
+necesita definir las funciones, clases, valores, etc. a exportar: ejecuta el
+módulo.
+
+Python define una forma de separar la funcionalidad del código de sus
+definiciones con el fin de poder crear código cuyas definiciones sean
+reutilizables mediante la importación en otro módulo, sin que tenga ninguna
+funcionalidad cuando esto ocurra, pero habilitando que tenga funcionalidades
+cuando sea llamado directamente.
+
+Un ejemplo de uso de esto puede ser un módulo de acceso a ficheros, por
+ejemplo, que visualice el contenido del fichero cuando se llame de forma
+directa pero que cuando se importe únicamente aporte las funciones de lectura y
+escritura sin leer y mostrar ningún fichero.
+
+Para que los módulos puedan tener esta doble vida, python define la variable
+`__name__` que representa en qué nivel del *scope* se está ejecutando el módulo
+actual. La variable `__name__` toma el valor del nombre del módulo cuando éste
+está siendo importado y el valor `__main__` cuando ha sido llamado de forma
+directa o está siendo ejecutado en la REPL. `__main__` es el *scope* global de
+los programas, por lo que cuando algo se declara en él, implica que es el
+programa principal.
+
+Para poder diferenciar cuándo se ha ejecutado un módulo de forma directa y
+cuando se ha importado se utiliza lo que se conoce como `__main__` *guard*:
+
+``` python
+if __name__ == "__main__":
+ # Este bloque sólo se ejecuta cuando el módulo es el principal
+```
+
+Aunque igual es un poco incómodo de entender de primeras, encontrarás esta
+estructura en casi cualquier módulo de código python. Se utiliza
+constantemente, incluso para los casos en los que no se pretende que el código
+pueda importarse. Es una buena práctica incluir el *guard* para separar la
+ejecución de las definiciones, de este modo, quien quiera saber cuál es la
+funcionalidad del módulo tendrá mucho más fácil la búsqueda.
+
+Puedes leer más sobre este tema en la documentación de python[^main-guard].
+
+[^main-guard]: <https://docs.python.org/3/library/__main__.html>
+
+
+Siguiendo este concepto, también existe el un estándar de nomenclatura de
+ficheros. El nombre `__main__.py` hace referencia al fichero que contiene el
+código que se incluiría dentro del *guard* y será el fichero que python buscará
+ejecutar siempre que se le pida ejecutar un paquete o un directorio sin
+especificar qué módulo debe lanzar. Por ejemplo, ejecutar `python .`[^dot] en
+la shell de sistema es equivalente a ejecutar `python __main__.py`.
+
+[^dot]: `.` significa directorio actual en cualquiera de los sistemas
+ operativos comunes.
+
+
+## Lo que has aprendido
+
+En este capítulo corto has aprendido lo necesario sobre importación de módulos
+y ejecución de código. Conocer en detalle el patrón de búsqueda de módulos de
+python es primordial para evitar problemas en el futuro y organizar los
+proyectos de forma elegante.
+
+Además, el `__main__` *guard* era una de las últimas convenciones de uso común
+que quedaban por explicar y una vez vista ya eres capaz de leer proyectos de
+código fuente que te encuentres por ahí sin demasiados problemas.
+
+Los próximos capítulos, basándose en lo aprendido en éste, te mostrarán cómo
+instalar nuevas dependencias y cómo preparar tu propio código para que pueda
+ser instalado de forma limpia y elegante.