martes, 3 de enero de 2017

T1. PAQUETES (PACKAGES) DE PYTHON: ORDENANDO EL ARMARIO.

COLONIA DE PALOMERAS, BARRANCOS DE GÜÍMAR, SUR DE TENERIFE.
   
      No perdamos de vista, insistimos en ello, que los módulos nos permiten organizar mucho mejor nuestro código, lo aclara y lo vuelve más eficaz. A este concepto contribuye también el de PAQUETE (package) de Python.
Un paquete  no es otra que un directorio, una carpeta (folder). Recordemos que un directorio es un género de contenedor virtual donde se almacenan, o pueden almecenarse, por una parte, subdirectorios que, a su vez, pueden contener otro subdirectorios hasta un número n de los mismos con archivos (o no) en su interior ("árbol de subdirectorios") o, por otra parte, archivos (file) directamente, sin incluirlos en subdirectorio alguno. Los archivos que se almacenan en un directorio se agrupan en función de la relación procedimental que guardan entre sí, por ejemplo, si 'coche' fuera un paquete, sus ficheros, módulos en Python, podrían ser motor.py, chasis.py, carburador.py, cajadecambios.py, conducción.py, depósito_combustible.py, etc..., pero no hidrodinámica.py, que podría integrarse en un paquete llamado 'náutica', o cocina_vegana.py, que podría integrarse en un paquete llamado 'agricultura_ecológica' o en otro que pudiera llamarse 'formas_de_alimentación', o incluso en ambos.
Lo podemos ver sintéticamente en el siguiente esquema:



Partiendo de un directorio, el intérprete de Python genera de manera automática, sin que nosotros tengamos que hacer nada, un "espacio de nombre" (namespace) de las variables contenidas en dicho directorio con la ortografía característica de los mismos.
Pero..., claro. Aunque ya hemos empleado este concepto de namespace en alguna que otra ocasión, no sabemos todavía lo que es. Hagamos, pues, una aproximación al menos a lo que significa el "espacio de nombres".
En Python, el concepto de "nombre" (name) es muy similar al concepto de que ya conocemos de "variable" en el sentido de "poner nombre" a una variable (referencia a un espacio de la memoria donde almacenar un dato o un conjunto de ellos): el nombre (name) sería la "referencia". Sin embargo, debemos aportar un añadido.
Veamos.
En Python se le puede poner nombre a casi todo: a las variables ('a', 'frutas', 'pasivo', 'pares', etc.), a los valores/datos que almacenan las primeras (7, 12.98, ["a","b","c"], {"helio", "oxígeno", "argón", "neón"}, etc.), a las funciones definidas por el usuario (def) (def suma(a,b): s = a + b return s ==> el nombre de esta función es 'suma'), módulos ("chasis.py", "motor.py", "frenos.py", etc.), paquetes ("coche", "contabilidad", "registro_civil", etc.).
También en Python, todo lo que hemos visto hasta ahora son objetos: números (int, float, hex, oct,...), cadenas, tuplas, diccionarios, funciones, etc, son objetos. Y los objetos tienen nombre porque de alguna manera tienen que ser llamados para poderlos utilizar, para que sean útiles, para poder acceder a ellos.
Por simple deducción, un "espacio de nombres" es un constructo, una especie de listado virtual que Python, como hemos dicho, genera automáticamente en algún lugar inextricable de su cerebro binario, donde guarda y conserva amorosamente todos esos nombres de objetos (variables, datos, funciones, etc.).
Ya sabemos que un módulo no es otra cosa que un fichero, un archivo (file) que contiene código Python y que, a su vez, se almacena en un directorio (folder) concreto. Obviamente, para poder importarlo tal y como acabamos de ver en la entrada anterior, tenemos que "llamarlo por su nombre", esto es, aquéllo que hayamos escrito delante de la extensión .py. 
Necesariamente, cada módulo tiene que tener su propio nombre, un nombre distinto a los demás para que Python pueda reconocerlo e importarlo a nuestro código, tal y como le solicitamos. Si contáramos con dos módulos que comparten un mismo nombre que almacenan funciones/métodos distintos, que ejecutan acciones diferentes, al importar, llamar o invocar al módulo por su nombre, el intérprete de Python importará al primero que encuentre en su búsqueda sin tener en cuenta si se trata del módulo que queremos o no. Por esta razón, cada módulo recibe su propio "espacio de nombres" (namespace) global. 

     El concepto de namespace se extiende a todo lo que lleve nombre que, en Python, lo es casi todo porque, no lo olvidemos, todo son objetos y cada objeto lleva su propio nombre para poder invocarlo en cualquier momento del desarrollo de nuestro código. Y esta idea se extiende de manera un poco más especial a las variables, a los nombres, "referencias", de las variables de tal modo que, a menos que nosotros no le cambiemos su nombre de manera expresa, éste mantenga su "espacio de nombres" y no se SOBREESCRIBA y nos encontremos en la tesitura de llamar a una variable que contenga datos no deseados, o bien que una variable que contiene datos concretos asuma un nombre nuevo que no le hemos proporcionado de manera expresa. ¡Ay mi peluda cabecita!

La única excepción, para este caso, lo constituyen los módulos anidados, de estructura similar a las listas anidadas que estudiamos en su momento, donde la propia jerarquía a la hora de establecer el orden en que se escriben los módulos determina qué módulo con el mismo nombre debe tomarse en lugar del otro.



Sin embargo, como los namespaces también están perfectamente aislados entre sí, recordemos una vez más que esto lo hace Python de manera automática, siempre y cuando se alojen en módulos con distinto nombre, podemos tener, por ejemplo, un módulo llamado enteros.py y otro llamado decimales.py, y en el interior de cada uno de ellos una función que podríamos llamar, por ejemplo, "quad", que calcula el cuadrado del número que se le pase como argumento. En ambos casos, establecemos, como en el esquema, una ruta de llamada distinta: enteros.quad, para el primer caso, o decimales.quad para el segundo, por lo que no habrá motivo para la confusión.

VISTA DE LA COSTA OCCIDENTAL DE ANAGA DESDE LAS INMEDIACIONES DEL FARO DE ANAGA, NORESTE DE TENERIFE.

Ahora que ya comprendemos un poco mejor eso del "espacio de nombres" (namespace) podemos continuar con nuestra exposición.
En el caso de nuestro módulo de ejemplo que ya hemos visto, at.py, Python creará tres "espacios de nombres" distintos, uno por cada por cada fichero que tenemos en él, en función del nombre (name) que le hemos dado: '__area_triangulo__', '__altura_triangulo__' y '__perimetro_triangulo__'. Estos son los namespaces que ha creado Python automáticamente que contiene nuestro módulo at.py.
Y sí, con esa curiosa ortografía de dobles guión bajo delante y detrás del nombre, sí. Observemos el siguiente esquema:


Así, podremos organizar nuestro código de manera mucho más fluida y eficaz, recordémoslo: esta conversión a namespace la hace Python por sí mismo.
Aclarado esto, vamos a proceder a crear nuestro primer paquete, nuestro primer package. Para verlo mejor, crearemos un segundo módulo al que llamaremos circulo.py y que contendrá dos funciones: una para calcular el área del círculo (area_circulo) y otra para calcular el perímetro (circunferencia).



Aplicamos los procedimientos de guardado que ya vimos en el capítulo dedicado a módulos y, como podemos ver, ya tenemos ambos, at.py y circulos.py, guardaditos y bien calentitos en la librería estándar (stdlib) de Python listos para ser invocados cuando los necesitemos.


Es el momento adecuado para crear un paquete donde almacenar nuestros dos módulos at.py y circulos.py: construimos un nuevo fichero al que llamamos 'pack_areas' de la misma manera que hemos hecho con los módulos y, en él, importaremos los módulos que queramos guardar.



Comprobamos, por si las moscas, que nuestro nuevo módulo/directorio está incluido en la stdlib de Python:



... y comprobar seguidamente su completa operatividad con llamadas a funciones distintas de ambos módulos, at.py y circulos.py:


Sin embargo, desde el punto de vista del intérprete de Python tan sólo tenemos una carpeta/directorio que contiene dos módulos .py. Si queremos transformarlo en un paquete (package) Python con todas las de la ley, tan solo tenemos que añadir un archivo de inicio llamado: '__init__'.py, que estará por encima (jerárquicamente hablando, en el árbol de ficheros) de todos los demás módulos Python.
Este fichero/módulo '__init__'.py puede estar perfectamente vacío, sin contenido alguno. Y esto es incluso lo recomendable, aunque es posible guardar contenido en él.
Para hacerlo debemos proceder de la siguiente manera: abrimos nuestro IDLE de Python y creamos un fichero con ese mismo nombre de la manera habitual.




Podemos comprobar que ha sido almacenado en la librería estándar de Python acudiendo al comando help():



Efectivamente, así ha sido.. Conviene tener presente que cada vez que construyamos un paquete tendremos que inicializarlo (de aquí el constructo __init__) con este fichero, colocando tras él todos aquellos módulos que integran nuestro paquete. Por eso siempre es recomendable, por una parte, tenerlos vacíos y, por otra, copiar o construir unos cuantos para tenerlos siempre a nuestra disposición.
Podemos crear un nuevo fichero incluyendo al principio un fichero __init__ y el resto de módulos a continuación, para sobreescribir luego el paquete `pack__areas', conservando el nombre original. Podemos hacer las pertinentes llamadas para comprobar la fiabilidad y la utilidad de nuestro, ahora sí, package en toda regla.



Recapitulando, cada uno de nuestros archivos se llama en Python módulos, que deben tener una extensión o sufijo .py tras el nombre. Estos módulos, a su vez, pueden agruparse formando paquetes (packages). Un paquete es una carpeta o directorio que contiene archivos *.py. Pero para que un directorio pueda ser considerado un paquete, debe contener un "archivo de inicialización" que llamaremos __init__.py, que conviene dejar en blanco, vacío de contenido.



Es posible que en programas más grandes y complejos, a parte de directorios, tendremos que crear subdirectorios. O para ser más exactos, paquetes que contiene a su vez subpaquetes. Todos estos, junto con el paquete matriz, deben contener el módulo __init__.py como archivo de inicialización. Veamos un esquema de su estructura:



EL PITÓN VOLCÁNICO DE ANAMBRO EMERGIENDO ENTRE LAS NIEBLAS DE LA FRONDA HÚMEDA DE LA LAURISILVA DE ANAGA, NORESTE DE TENERIFE

      EL MÉTODO ESPECIAL __ALL__:

      Pueden darse situaciones en las que, pongamos por caso, queremos importar todos los archivos almacenados en un subpaquete, esto es, un paquete dependiente de otro de mayor jerarquía, una especie de 'paquete-padre' o paquete-raíz, que es lo que se entiende en Python como PAQUETES ANIDADOS. En estas situaciones, podría ocurrir que tardase mucho en cargarse los archivos o que, incluso, se produjeran respuestas no esperadas o indeseadas. Podría resultar más práctico cargar todos los módulos alojados en el mencionado subpaquete recurriendo a una única sentencia, lo que nos permitiría soslayar los posibles inconvenientes que hemos enumerado. ¿Cómo lo hacemos? Tendremos que editar el subpaquete y declarar una sentencia específica, para lo que utilizaremos una nomenclatura especial: __all__, sin sufijo, dado que no es ningún archivo de Python, como sí lo es el inicializador de un paquete (__init__.py) sino una variable, una variable "especial" que guarda una lista de todos los módulos contenidos en el subpaquete. Veamos la siguiente tabla.


Imaginemos ahora que queremos crear la variable __all__ para almacenar los tres módulos que se alojan en el subpaquete 'Técnicos_python'. Procederíamos de la siguiente manera:

                                                                        __all__ = ["Doc", "Pdf", "Txt"]





Ahora podemos realizar la siguiente importación:

                                                                from TEXTOS import Técnicos_python *

Lo que hará entonces el intérprete de Python es que cuando recorra el path, la ruta que lo lleva desde el paquete-raíz 'TEXTOS'  al subpaquete 'Técnicos_python', "mete la nariz" en su contenido y priorizará la variable __all__ sobre la importación módulo a módulo, resolviendo más eficazmente nuestra petición.
No parece tan complicado, ¿verdad?

TABAIBAS AL PIE DEL ROQUE DE JAMA, SUROESTE DE TENERIFE.