Módulos de Python: creación, importación y uso compartido

Los módulos son la unidad organizativa de más alto nivel en Python. Si está al menos un poco familiarizado con Python, probablemente no solo haya usado módulos listos, sino...

Introducción

Los módulos son la unidad organizativa de más alto nivel en Python. Si está al menos un poco familiarizado con Python, probablemente no solo haya usado módulos listos, sino que también haya creado algunos usted mismo. Entonces, ¿qué es exactamente un módulo? Módulos son unidades que almacenan código y datos, brindan reutilización de código para proyectos de Python y también son útiles para particionar los espacios de nombres del sistema. en paquetes independientes. Son independientes porque solo puede acceder a los atributos de un módulo después de importarlo. También puede entenderlos como paquetes de nombres, que al importarlos se convierten en atributos del objeto del módulo importado. De hecho, cualquier archivo de Python con una extensión .py representa un módulo.

En este artículo, comenzamos desde los conceptos básicos básicos de la creación e importación de módulos, hasta casos de uso de módulos más avanzados, para empaquetar y enviar sus módulos a un repositorio de software "oficial" de Python, estructurado respectivamente en tres partes: Creación de un módulo , Usar un módulo y Envío de un paquete a PyPI.

Creación de un módulo

Lo básico

Realmente no hay mucha filosofía en la creación de un módulo de Python ya que los archivos con un sufijo .py representan un módulo. Aunque no todos los archivos de Python están diseñados para importarse como un módulo. Los archivos de Python que se utilizan para ejecutarse como una aplicación de Python independiente (archivos de nivel superior) generalmente están diseñados para ejecutarse como secuencias de comandos e importarlos ejecutaría los comandos en la secuencia de comandos.

Los módulos que están diseñados para ser importados por otro código no ejecutarán ningún código, sino que solo expondrán sus nombres de nivel superior como atributos para el objeto importado. También es posible diseñar módulos Python de código de modo dual que podrían usarse para importar y ejecutar como un script de nivel superior.

Si bien las reglas de creación de módulos son bastante relajadas, hay una regla sobre la denominación de los módulos. Dado que los nombres de archivo de los módulos se convierten en nombres de variables en Python cuando se importan, no se permite nombrar módulos con palabras reservadas de Python. Por ejemplo, se puede crear un módulo for.py, pero no se puede importar porque "for" es una palabra reservada. Ilustremos lo que hemos mencionado hasta ahora en un ejemplo de "¡Hola mundo!".

1
2
3
4
5
6
# Module file: my_module.py

def hello_printer():
    print("Hello world!")

name = "John"
1
2
3
4
5
6
# Script file: my_script.py

import my_module

my_module.hello_printer()
print("Creator:", my_module.name)

'my_module.py' está diseñado como un módulo cuyo código se puede importar y reutilizar en otros archivos de Python. Puedes verlo por su contenido: no requiere ninguna acción, solo define funciones y variables. Por el contrario, 'my_script.py' está diseñado como un script de nivel superior que ejecuta el programa Python: llama explícitamente a una función hello_printer e imprime el valor de una variable en la pantalla.

Ejecutemos el archivo 'my_script.py' en la terminal:

1
2
3
4
$ python my_script.py

Hello world!
Creator: John

Como se señaló anteriormente, una conclusión importante de este primer ejemplo básico es que los nombres de archivo de los módulos son importantes. Una vez importados, se convierten en variables/objetos en el módulo del importador. Todas las definiciones de código de nivel superior dentro de un módulo se convierten en atributos de esa variable.

Por 'nivel superior' me refiero a cualquier función o variable que no esté anidada dentro de otra función o clase. Se puede acceder a estos atributos usando la declaración estándar <objeto>.<atributo> en Python.

En la siguiente sección, primero observamos el "panorama general" de los programas Python de varios archivos, y luego en los archivos Python "modo dual".

Arquitectura del programa

Cualquier programa de Python no trivial se organizaría en varios archivos, conectados entre sí mediante importaciones. Python, como la mayoría de los otros lenguajes de programación, utiliza esta estructura de programa modular, donde las funcionalidades se agrupan en unidades reutilizables. En general, podemos distinguir tres tipos de archivos en una aplicación Python de varios archivos:

  • archivo de nivel superior: un archivo de Python, o script, que es el punto de entrada principal del programa. Este archivo se ejecuta para iniciar su aplicación.
  • módulos definidos por el usuario: archivos de Python que se importan al archivo de nivel superior, o entre ellos, y brindan funcionalidades separadas. Por lo general, estos archivos no se inician directamente desde el símbolo del sistema y están hechos a la medida para el propósito del proyecto.
  • módulos de biblioteca estándar: módulos precodificados que están integrados en el paquete de instalación de Python, como herramientas independientes de la plataforma para interfaces del sistema, secuencias de comandos de Internet, construcción de GUI y otros. Estos módulos no son parte del ejecutable de Python en sí, sino parte de la biblioteca estándar de Python.

La Figura 1 muestra una estructura de programa de ejemplo con los tres tipos de archivos:

Python program module structure{.img-responsive}

Figura 1: una estructura de programa de ejemplo que incluye un script de nivel superior, módulos personalizados y módulos de biblioteca estándar.

En esta figura, el módulo 'top_module.py' es un archivo Python de nivel superior que importa las herramientas definidas en el módulo 'module1', pero también tiene acceso a las herramientas en 'module2' hasta 'module1 '. Los dos módulos personalizados utilizan los recursos de cada uno, así como otros módulos de la biblioteca estándar de Python. La cadena de importación puede ser tan profunda como quieras: no hay límite en el número de archivos importados, y pueden importarse entre sí, aunque hay que tener cuidado con [importación circular](/importaciones-circulares-de -python/).

Ilustremos esto a través de un ejemplo de código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# top_module.py
import module1
module1.print_parameters()
print(module1.combinations(5, 2))


# module1.py
from module2 import k, print_parameters
from math import factorial
n = 5.0
def combinations(n, k):
    return factorial(n) / factorial(k) / factorial(n-k)


# module2.py
import module1
k = 2.0
def print_parameters():
    print('k = %.f n = %.f' % (k, module1.n))

En el ejemplo anterior, 'top_module.py' es un módulo de nivel superior que será ejecutado por el usuario e importa herramientas de otros módulos a través de 'module1.py'. module1 y module2 son módulos definidos por el usuario, mientras que el módulo 'math' se importa de la biblioteca estándar de Python. Al ejecutar el script de nivel superior, obtenemos:

1
2
3
$ python top_module.py
k = 2 n = 5
10.0

Cuando se ejecuta un archivo Python de nivel superior, las declaraciones de su código fuente y las declaraciones dentro de los módulos importados se compilan en un formato intermedio conocido como [código de bytes] (https://en.wikipedia.org/wiki/Bytecode) , que es un formato independiente de la plataforma. Los archivos de código de bytes de los módulos importados se almacenan con una extensión .pyc en el mismo directorio que el archivo .py para las versiones de Python hasta 3.2, y en el directorio __pycache__ en el directorio de inicio del programa en Python 3.2+.

1
2
$ ls __pycache__/
module1.cpython-36.pyc  module2.cpython-36.pyc

Código de modo dual

Como se mencionó anteriormente, los archivos de Python también se pueden diseñar como módulos importables y scripts de nivel superior. Es decir, cuando se ejecuta, el módulo de Python se ejecutará como un programa independiente y, cuando se importe, actuará como un módulo importable que contiene definiciones de código.

Esto se hace fácilmente usando el atributo __name__ , que se integra automáticamente en cada módulo. Si el módulo se ejecuta como un script de nivel superior, el atributo __name__ será igual a la cadena "__main__", de lo contrario, si se importa, contendrá el nombre del módulo real.

Aquí hay un ejemplo de código de modo dual:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# hiprinter.py

# Name definitions part
multiply = 3
def print_hi():
    print("Hi!" * multiply)

# Stand-alone script part
if __name__ == '__main__':
    print_hi()

El archivo anterior 'hiprinter.py' define una función, que se expondrá al cliente cuando se importe. Si el archivo se ejecuta como un programa independiente, la misma función se llama automáticamente. La diferencia aquí, en comparación con el ejemplo de 'my_script.py' en la Sección Los basicos, es que cuando se importa 'hiprinter.py', no ejecutará el código anidado bajo if __name__ == declaración '__main__'.

1
2
3
4
# Terminal window

$ python hiprinter.py
Hi!Hi!Hi!
1
2
3
4
5
# Python interpreter

>> import hiprinter
>> hiprinter.print_hi()
Hi!Hi!Hi!

El código de modo dual es muy común en la práctica, y especialmente útil para pruebas unitarias: mientras que las variables y funciones se definen como nombres de nivel superior en el archivo, la parte dentro de la instrucción if puede servir como un área de prueba del archivo. nombres definidos anteriormente.

Uso de un módulo

Importar estados de cuenta

El ejemplo en la Sección Arquitectura del programa fue útil para ver la diferencia entre dos sentencias de importación: import y from. La principal diferencia es que import carga todo el módulo como un solo objeto, mientras que from carga propiedades y funciones específicas del módulo. La importación de nombres con la declaración from se puede usar directamente en el módulo de importación, sin llamar al nombre del objeto importado.

El uso de la declaración from solo está permitido en el nivel superior del archivo del módulo en Python 3.x, y no dentro de una función. Python 2.x permite usarlo en una función, pero emite una advertencia. En cuanto al rendimiento, la declaración from es más lenta que import porque hace todo el trabajo que hace import: revisar todo el contenido del módulo importado y luego realiza un paso adicional al seleccionar los nombres apropiados para importar .

También hay una tercera declaración de importación desde * que se usa para importar todos los nombres de nivel superior del módulo importado y usarlos directamente en la clase del importador. Por ejemplo, podríamos haber usado:

1
from module2 import *

Esto importaría todos los nombres (variables y funciones) del archivo module2.py. Este enfoque no se recomienda debido a la posible duplicación de nombres: los nombres importados podrían sobrescribir los nombres ya existentes en el módulo del importador.

Ruta de búsqueda del módulo

Un aspecto importante al escribir aplicaciones de Python modulares es ubicar los módulos que deben importarse. Si bien los módulos de la biblioteca estándar de Python están configurados para ser accesibles globalmente, la importación de módulos definidos por el usuario a través de los límites del directorio puede resultar más complicada.

Python usa una lista de directorios en los que busca módulos, conocida como ruta de búsqueda. La ruta de búsqueda se compone de directorios que se encuentran en lo siguiente:

  1. Directorio de inicio del programa. La ubicación del script de nivel superior. Tenga en cuenta que el directorio de inicio puede no ser el mismo que el directorio de trabajo actual.
  2. Directorios PYTHONPATH. Si se establece, la variable de entorno PYTHONPATH define una concatenación de directorios definidos por el usuario donde el intérprete de Python debe buscar módulos.
  3. Directorios de biblioteca estándar. Estos directorios se configuran automáticamente con la instalación de Python y siempre se buscan.
  4. Directorios listados en archivos .pth. Esta opción es una alternativa a PYTHONPATH, y funciona agregando sus directorios, uno por línea, en un archivo de texto con el sufijo .pth, que debe colocarse en el directorio de instalación de Python, que generalmente es /usr/ local/lib/python3.6/ en una máquina Unix o C:\Python36\ en una máquina Windows.
  5. El directorio paquetes del sitio. Este directorio es donde se agregan automáticamente todas las extensiones de terceros.

PYTHONPATH es probablemente la forma más adecuada para que los desarrolladores incluyan sus módulos personalizados en la ruta de búsqueda. Puede verificar fácilmente si la variable está configurada en su computadora, lo que en mi caso da como resultado:

1
2
$ echo $PYTHONPATH
/Users/Code/Projects/:

Para crear la variable en una máquina con Windows, debe usar las instrucciones en "Panel de control -> Sistema -> Avanzado", mientras que en MacOS y otros sistemas Unix es más fácil agregar la siguiente línea a * ~/.bashrc* o ~/.bash_profile, donde sus directorios están concatenados con un signo de dos puntos (":").

1
export PYTHONPATH=<Directory1:Directory2:...:DirectoryN>:$PYTHONPATH".

This method is very similar to agregando directorios a su Unix $PATH.

Una vez que se encuentran todos los directorios en la ruta de búsqueda durante el inicio del programa, se almacenan en una lista que se puede explorar con sys.path en Python. Por supuesto, también podría agregar un directorio a sys.path y luego importar sus módulos, que solo modificarán la ruta de búsqueda durante la ejecución del programa.

De todos modos, las opciones PYTHONPATH y .pth permiten una modificación más permanente de la ruta de búsqueda. Es importante saber que Python escanea la cadena de la ruta de búsqueda de izquierda a derecha, por lo que los módulos dentro de los directorios enumerados más a la izquierda pueden sobrescribir los que tienen el mismo nombre en la parte más a la derecha. Tenga en cuenta que las rutas de búsqueda de módulos solo son necesarias para importar módulos en diferentes directorios.

Como se muestra en el siguiente ejemplo, la cadena vacía al principio de la lista es para el directorio actual:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import sys
sys.path

['',
 '/Users/Code/Projects',
 '/Users/Code/Projects/Blogs',
 '/Users/Code/anaconda3/lib/python36.zip',
 '/Users/Code/anaconda3/lib/python3.6',
 '/Users/Code/anaconda3/lib/python3.6/site-packages',
 '/Users/Code/anaconda3/lib/python3.6/site-packages/IPython/extensions',
 '/Users/Code/.ipython']

Como resultado final, organizar su programa Python en múltiples módulos interconectados es bastante sencillo si su programa está bien estructurado: en porciones de código independientes y agrupadas de forma natural. En programas más complejos o no tan bien estructurados, la importación puede convertirse en una carga y deberá abordar temas de importación más avanzados.

Recargas de módulos

Gracias al almacenamiento en caché, un módulo solo se puede importar una vez por proceso. Dado que Python es un lenguaje interpretado, ejecuta el código del módulo importado una vez que llega a una declaración import o from. Las importaciones posteriores dentro del mismo proceso (por ejemplo: el mismo intérprete de Python) no volverán a ejecutar el código del módulo importado. Simplemente recuperará el módulo del caché.

Aquí hay un ejemplo. Reutilicemos el código anterior en 'my_module.py', impórtelo en un intérprete de Python, luego modifique el archivo y vuelva a importarlo.

1
2
3
4
5
6
7
8
9
>> import my_module
>> print(my_module.name)
John

# Now modify the 'name' variable in 'my_module.py' into name = 'Jack' and reimport the module

>> import my_module
>> print(my_module.name)
John

Para deshabilitar el almacenamiento en caché y habilitar la reimportación de módulos, Python proporciona una función de “recarga”. Intentémoslo en la misma ventana de Python que antes:

1
2
3
4
5
>> from imp import reload  # Python3.x
>> reload(my_module)
<module 'my_module' from '/Users/Code/Projects/small_example/my_module.py'>
>> print(my_module.name)
Jack

La función recargar modifica el módulo en el lugar. Es decir, sin afectar a otros objetos que hacen referencia al módulo importado. Puede notar que la función también devuelve el módulo en sí mismo, dando su nombre y ruta de archivo. Esta característica es especialmente útil en la fase de desarrollo, pero también en proyectos más grandes.

Por ejemplo, para los programas que necesitan una conectividad permanente a un servidor, es mucho más costoso reiniciar toda la aplicación que hacer una recarga dinámica, o recargar en caliente para usar durante el desarrollo.

Paquetes de módulos

Al importar nombres de módulos, en realidad carga archivos de Python almacenados en algún lugar de su sistema de archivos. Como se mencionó anteriormente, los módulos importados deben residir en un directorio, que se encuentra en la ruta de búsqueda de módulos (sys.path). En Python hay más que estas "importaciones de nombres": puede importar un directorio completo que contenga archivos de Python como un paquete de módulos. Estas importaciones se conocen como importaciones de paquetes.

Entonces, ¿cómo se importan paquetes de módulos? Vamos a crear un directorio llamado 'mydir' que incluye un módulo 'mod0.py' y dos subdirectorios 'subdir1' y 'subdir2', que contienen 'mod1.py' y \ módulos ‘mod2.py' respectivamente. La estructura del directorio se ve así:

1
2
3
4
5
6
7
8
$ ls -R mydir/
mod0.py subdir1 subdir2

my_dir//subdir1:
mod1.py

my_dir//subdir2:
mod2.py

El enfoque habitual explicado hasta ahora era agregar las rutas 'mydir', 'subdir1' y 'subdir2' a la ruta de búsqueda del módulo (sys.path), para poder importar \ ‘mod0.py', 'mod1.py' y 'mod2.py'. Esto podría convertirse en una gran sobrecarga si sus módulos se distribuyen en muchos subdirectorios diferentes, lo que suele ser el caso. De todos modos, las importaciones de paquetes están aquí para ayudar. Funcionan importando el nombre de la propia carpeta.

Este comando, por ejemplo, no está permitido y dará como resultado un error InvalidSyntax:

1
2
3
4
5
>> import /Users/Code/Projects/mydir/
  File "<stdin>", line 1
    import /Users/Code/Projects/mydir/
           ^
SyntaxError: invalid syntax

La forma correcta de hacerlo es configurar solo el directorio contenedor '/Users/Code/Projects/' en la ruta de búsqueda de su módulo (agregándolo a la variable de entorno PYTHONPATH o enumerándolo en un archivo .pth) y luego importe sus módulos usando la sintaxis punteada. Estas son algunas importaciones válidas:

1
2
3
>> import mydir.mod0
>> import mydir.subdir1.mod1 as mod1
>> from mydir.subdir2.mod2 import print_name  # print_name is a name defined within mod2.py

Probablemente haya notado anteriormente que algunos directorios de Python incluyen un archivo __init__.py. En realidad, este era un requisito en Python2.x para decirle a Python que su directorio es un paquete de módulos. El archivo __init__.py también es un archivo Python normal que se ejecuta cada vez que se importa ese directorio y es adecuado para inicializar valores, p. para realizar la conexión a una base de datos.

De todos modos, en la mayoría de los casos estos archivos se dejan vacíos. En Python3.x, estos archivos son opcionales y puede usarlos si es necesario. Las siguientes líneas muestran cómo los nombres definidos en __init__.py se convierten en atributos del objeto importado (el nombre del directorio que lo contiene).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# __init__.py file in mydir/subdir1/ with code:
param = "init subdir1"
print(param)


# Import it from a Python interpreter
>> import mydir.subdir1.mod1
init subdir1


# param is also accessible as an attribute to mydir.subdir1 object
>> print(mydir.subdir1.param)
init subdir1

Otro tema importante cuando se habla de paquetes de módulos son las importaciones relativas. Las importaciones relativas son útiles cuando se importan módulos dentro del propio paquete. En este caso, Python buscará el módulo importado dentro del alcance del paquete y no en la ruta de búsqueda del módulo.

Demostraremos un caso útil con un ejemplo:

1
2
3
4
5
6
7
# mydir/subdir1/mod1.py
import mod2


# In Python interpreter:
>> import mydir.subdir1.mod1
ModuleNotFoundError: No module named 'mod2'

La línea importar mod2 le dice a Python que busque el módulo 'mod2' en la ruta de búsqueda del módulo y, por lo tanto, no tiene éxito. En cambio, una importación relativa funcionará bien. La siguiente declaración de importación relativa utiliza un doble punto ("..") que denota el padre del paquete actual ('mydir/'). Se debe incluir el siguiente subdir2 para crear una ruta relativa completa al módulo mod2.

1
2
# mydir/subdir1/mod1.py
from ..subdir2 import mod2

Las importaciones relativas son un tema muy amplio y podrían ocupar un capítulo completo de un libro. También difieren mucho entre las versiones Python2.x y 3.x. Por ahora, solo mostramos un caso útil, pero debería haber más para seguir en publicaciones de blog separadas.

Y hablando de Python 2.x, el soporte para esta versión termina en 2020, por lo que en los casos en que hay una gran diferencia entre las versiones de Python, como en las importaciones relativas, es mejor para centrarse en la versión 3.x.

Envío de un paquete a PyPi

Hasta ahora, ha aprendido a escribir módulos de Python, distinguir entre módulos importables y de nivel superior, usar módulos definidos por el usuario a través de los límites del directorio, modificar la ruta de búsqueda del módulo y crear/importar paquetes de módulos, entre otras cosas. Una vez que haya creado un software útil, empaquetado en un paquete de módulos, es posible que desee compartirlo con la gran comunidad de Python. Después de todo, la comunidad construye y mantiene Python.

El Índice de paquetes de Python (PyPI) es un repositorio de software para Python, que actualmente contiene más de 120 000 paquetes (al momento de escribir este artículo). Es posible que haya instalado módulos antes desde este repositorio usando el comando pip.

Por ejemplo, la siguiente línea descargará e instalará la biblioteca Numpy para computación científica:

1
$ pip install numpy

Hay más información sobre la instalación de paquetes con pip aquí. Pero, ¿cómo contribuyes con tu propio paquete? Aquí hay algunos pasos para ayudarte con eso.

  • En primer lugar, satisfacer los requisitos de envasado y distribución. Hay dos pasos necesarios aquí:
    • Install pip, setuptools, and wheel. More information on that aquí.
    • Install enroscarse, which is used to upload your project to PyPI
1
$ pip install twine
  • El siguiente paso es configurar tu proyecto. En general, esto significa agregar algunos archivos de Python a su proyecto que contendrá la información de configuración, guías de uso, etc. PyPI proporciona un ejemplo [proyecto de muestra] (https://github.com/pypa/sampleproject) que puede utilizar como guía. Estos son los archivos más importantes que debe agregar:
    • setup.py: This file needs to be added to the root of your project, and serves as an installation command line interface. It must contain a setup() function which will accept as arguments information such as: project name, version, description, license, project dependencies, etc.
    • README.rst: A text file describing your package.
    • licence.txt: A text file containing your software licence. More information on elegir una licencia, via GitHub.
  • Empaqueta tu proyecto. El tipo de paquete más utilizado es 'rueda', aunque también puede proporcionar el requisito mínimo como 'distribución/paquete de fuente'. Aquí debe usar el archivo 'setup.py' del paso anterior. Ejecutar uno de los siguientes comandos creará un directorio 'dist/' en la raíz de su proyecto, que contiene los archivos para cargar en PyPI.
1
2
3
4
5
6
# Package as source distribution
$ python setup.py sdist


# Package as wheel supporting a single Python version
$ python setup.py bdist_wheel
  • El paso final es subir su distribución a PyPI. Básicamente hay dos pasos aquí:
    • Create a PyPI account.
    • Upload the contents of the 'dist/' directory created in the previous step. Here you might want to upload a test first using the Sitio de prueba de PyPI.
1
$ twine upload dist/*

Eso es practicamente todo. Para más información, el sitio web PyPI tiene todas las instrucciones detalladas si te quedas atascado.

Conclusión

Esta publicación tenía como objetivo guiarlo desde los conceptos básicos de los módulos de Python (creación e importación de sus primeros módulos importables), a temas un poco más avanzados (modificación de la ruta de búsqueda, paquetes de módulos, recargas y algunas importaciones relativas básicas), para enviar su Paquete de Python al repositorio de software de Python PyPI.

Hay mucha información sobre este tema y no pudimos cubrir todo en esta publicación, por lo que es posible que no pueda abordar todos estos pasos y enviar un paquete oficial dentro del tiempo de lectura de esta publicación. Sin embargo, cada paso debe ser una breve introducción para guiarlo en su camino de aprendizaje.

Referencias

Licensed under CC BY-NC-SA 4.0