Módulos de Python: Crear, importar y compartir

    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? Los módulos son unidades que almacenan código y datos, brindan reutilización de código a 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 cuando se importan se convierten en atributos del objeto de 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, hasta empaquetar y enviar sus módulos a un repositorio de software Python “oficial”, estructurado respectivamente en tres partes: Creación de un módulo, Uso de un módulo y Enviar un paquete a PyPI.

    Crear un módulo

    Los basicos

    Realmente no hay mucha filosofía en la creación de un módulo 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 scripts y, al importarlos, se ejecutarían los comandos en el script.

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

    Si bien las reglas de creación de módulos son bastante relajadas, hay una regla sobre el nombre 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 “para” es una palabra reservada. Ilustremos lo que hemos mencionado hasta ahora en un “¡Hola mundo!” ejemplo.

    # Module file: my_module.py
    
    def hello_printer():
        print("Hello world!")
    
    name = "John"
    
    # 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 ver eso 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_printere imprime el valor de una variable en la pantalla.

    Ejecutemos el archivo ‘my_script.py’ en la terminal:

    $ 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 de importación. 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. Luego se puede acceder a estos atributos usando la <object>.<attribute>declaración estándar en Python.

    En la siguiente sección, primero miramos el “panorama general” de los programas Python de múltiples archivos, y luego en los archivos Python de “modo dual”.

    Arquitectura del programa

    Cualquier programa 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 múltiples archivos:

    • archivo de nivel superior: un archivo de Python, o secuencia de comandos, 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 sí, y proporcionan funcionalidades independientes. Por lo general, estos archivos no se inician directamente desde el símbolo del sistema y están hechos a 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 forman parte del ejecutable de Python en sí, sino de la biblioteca estándar de Python .

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

    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 de Python de nivel superior que importa las herramientas definidas en el módulo ‘módulo1’, pero también tiene acceso a las herramientas de ‘módulo2’ a ‘módulo1’. Los dos módulos personalizados utilizan los recursos del otro, así como otros módulos de la biblioteca estándar de Python. La cadena de importación puede ser tan profunda como desee: no hay límite en la cantidad de archivos importados y se pueden importar entre sí, aunque debe tener cuidado con la importación circular.

    Ilustremos esto con un ejemplo de código:

    # 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’. module1y module2son módulos definidos por el usuario, mientras que el módulo ‘matemático’ se importa de la biblioteca estándar de Python. Al ejecutar el script de nivel superior, obtenemos:

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

    Cuando se ejecuta un archivo Python de nivel superior, sus declaraciones de código fuente y las declaraciones dentro de los módulos importados se compilan en un formato intermedio conocido como código de bytes , 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+.

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

    Código de modo dual

    Como se mencionó anteriormente, los archivos Python también se pueden diseñar como módulos importables y como scripts de nivel superior. Es decir, cuando se ejecuta, el módulo 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:

    # hiprinter.py
    
    # Name definitions part
    multiply = 3
    def print_hi():
        print("Hi!" * multiply)
    
    # Stand-alone script part
    if __name__ == '__main__':
        print_hi()
    

    El archivo ‘hiprinter.py’ anterior define una función, que estará expuesta 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 ‘my_script.py’ en la sección Conceptos básicos, es que cuando se importa ‘hiprinter.py’, no ejecutará el código anidado bajo la if __name__ == '__main__'declaración.

    # Terminal window
    
    $ python hiprinter.py
    Hi!Hi!Hi!
    
    # 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 ifdeclaración puede servir como un área de prueba de los nombres definidos anteriormente. .

    Usando un módulo

    Declaraciones de importación

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

    El uso de la fromdeclaración solo está permitido en el nivel superior del archivo de 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 fromdeclaración es más lenta importporque hace todo el trabajo que importhace: revisa todo el contenido del módulo importado y luego realiza un paso adicional para seleccionar los nombres adecuados para la importación.

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

    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 de importación.

    Ruta de búsqueda del módulo

    Un aspecto importante al escribir aplicaciones modulares de Python 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 complicado.

    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:

    • Directorio de inicio del programa. La ubicación del script de nivel superior. Tenga en cuenta que el directorio de inicio puede no coincidir con el directorio de trabajo actual.
    • PYTHONPATHdirectorios. Si se establece, la PYTHONPATHvariable de entorno define una concatenación de directorios definidos por el usuario donde el intérprete de Python debe buscar módulos.
    • Directorios de bibliotecas estándar. Estos directorios se configuran automáticamente con la instalación de Python y siempre se buscan.
    • Directorios enumerados en archivos .pth. Esta opción es una alternativa PYTHONPATHy 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.
    • El directorio de paquetes de sitio. Este directorio es donde se agregan automáticamente todas las extensiones de terceros.

    PYTHONPATHes 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 resulta en:

    $ 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 ~ /. archivos bash_profile, donde sus directorios están concatenados con un signo de dos puntos (“:”).

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

    Este método es muy similar a agregar directorios a su Unix $ PATH.

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

    PYTHONPATHLas opciones de todos modos 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 tanto, los módulos dentro de los directorios listados 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:

    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 conclusión, organizar su programa Python en múltiples módulos interconectados es bastante sencillo si su programa está bien estructurado: en porciones de código autocontenidas y agrupadas naturalmente. 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 se puede importar solo 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 importo 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 de la caché.

    He aquí un ejemplo. Reutilicemos el código anterior en ‘my_module.py’, importémoslo en un intérprete de Python, luego modifiquemos el archivo y volvamos a importarlo.

    >> 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 reloadfunción. Probémoslo en la misma ventana de Python que antes:

    >> 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 reloadfunción modifica el módulo in situ. Es decir, sin afectar otros objetos que hagan referencia al módulo importado. Puede notar que la función también devuelve el módulo en sí, 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 enumera en la ruta de búsqueda de su módulo ( sys.path). En Python hay más que estas “importaciones de nombres”; de hecho, puede importar un directorio completo que contenga archivos de Python como un paquete de módulo. Estas importaciones se conocen como importaciones de paquetes.

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

    $ 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, que suele ser el caso. De todos modos, las importaciones de paquetes están aquí para ayudar. Trabajan importando el nombre de la carpeta en sí.

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

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

    La forma correcta de hacerlo es establecer solo el directorio contenedor ‘/ Usuarios / Código / Proyectos /’ en la ruta de búsqueda de su módulo (agregarlo a la PYTHONPATHvariable de entorno o enumerarlo en un archivo .pth) y luego importar sus módulos usando el sintaxis punteada. Estas son algunas importaciones válidas:

    >> 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, esto 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, por ejemplo, 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).

    # __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 al importar 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:

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

    La import mod2línea 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 punto doble (“..”) que denota el padre del paquete actual (‘mydir /’). Se debe incluir el siguiente subdir2 para crear una ruta relativa completa al módulo mod2.

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

    Las importaciones relativas son un tema enorme y podrían ocupar todo un capítulo de un libro. También difieren mucho entre las versiones Python2.xy 3.x. Por ahora, solo hemos mostrado 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 finaliza en 2020 , por lo que en los casos en los que hay una gran diferencia entre las versiones de Python, como en las importaciones relativas, es mejor centrarse en la versión 3.x.

    Enviar un paquete a PyPi

    Hasta ahora, ha aprendido a escribir módulos Python, distinguir entre módulos importables y de nivel superior, utilizar 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, Python es creado y mantenido por la comunidad.

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

    Por ejemplo, la siguiente línea descargará e instalará la biblioteca Numpy para informática científica:

    $ pip install numpy
    

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

    • Primero, satisfaga los requisitos de empaque y distribución. Aquí se necesitan dos pasos:
      • Instale pip, setuptools y wheel. Más información sobre eso aquí .
      • Instale el hilo , que se utiliza para cargar su proyecto en PyPI
    $ pip install twine
    
    • El siguiente paso es configurar su proyecto. En general, esto significa agregar algunos archivos de Python a su proyecto que contendrán la información de configuración, guías de uso, etc. PyPI proporciona un proyecto de muestra de ejemplo que puede usar como guía. Estos son los archivos más importantes que debe agregar:
      • setup.py: este archivo debe agregarse a la raíz de su proyecto y sirve como una interfaz de línea de comandos de instalación. Debe contener una setup()función que acepte como argumentos información como: nombre del proyecto, versión, descripción, licencia, dependencias del proyecto, etc.
      • README.rst: un archivo de texto que describe su paquete.
      • licence.txt: un archivo de texto que contiene su licencia de software. Más información sobre cómo elegir una licencia , a través de 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 origen’. Aquí debe utilizar el archivo ‘setup.py’ del paso anterior. La ejecución de uno de los siguientes comandos creará un directorio ‘dist /’ en la raíz de su proyecto, que contiene los archivos para cargar en PyPI.
    # Package as source distribution
    $ python setup.py sdist
    
    
    # Package as wheel supporting a single Python version
    $ python setup.py bdist_wheel
    
    • El último paso es cargar su distribución en PyPI. Básicamente, aquí hay dos pasos:
      • Crea una cuenta PyPI.
      • Cargue el contenido del directorio ‘dist /’ creado en el paso anterior. Aquí es posible que desee cargar una prueba primero utilizando el sitio de prueba de PyPI .
    $ twine upload dist/*
    

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

    Conclusión

    Esta publicación tenía la intención de guiarlo desde los conceptos básicos básicos de los módulos de Python (crear e importar sus primeros módulos importables), a temas un poco más avanzados (modificar 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.

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *