diff options
-rw-r--r-- | src/06_ejec_mod.md | 271 |
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. |