2.19. Escribir módulos

Cualquier archivo .py es un módulo. Repartir un script en crecimiento entre varios archivos mantiene cada archivo corto y permite compartir los ayudantes comunes entre scripts.

2.19.1. Dividir un script

Saca un grupo de funciones relacionadas a su propio archivo:

camera_utils.py:

def banner():
    print("OpenMV")

def label(text):
    return "[" + text + "]"

main.py:

import camera_utils

camera_utils.banner()
print(camera_utils.label("ready"))

Salida:

OpenMV
[ready]

Los dos archivos conviven uno junto al otro en el mismo directorio. Cuando se ejecuta main.py, import camera_utils lee camera_utils.py una vez, ejecuta sus sentencias de nivel superior y enlaza el objeto módulo resultante al nombre camera_utils en main. Un segundo import camera_utils desde cualquier otro sitio devuelve el mismo objeto: los módulos se almacenan en caché tras su primera carga.

El nombre de un módulo procede de su nombre de archivo, así que camera_utils.py se importa como import camera_utils.

2.19.2. Módulos de varios archivos (paquetes)

Un módulo también puede ser un directorio de archivos en lugar de un único .py. El nombre del directorio pasa a ser el nombre del módulo, y los archivos de su interior son sus submódulos:

camera_utils/
    __init__.py
    text.py
    timing.py

__init__.py es el archivo que se ejecuta cuando se importa el propio paquete; puede estar vacío, o puede reexportar nombres seleccionados de los submódulos. A los submódulos se accede con un nombre con puntos:

import camera_utils.text
from camera_utils.timing import elapsed

camera_utils.text.label("ready")

Dentro del paquete, los submódulos pueden acceder entre sí ya sea por el nombre completo con puntos o mediante una importación relativa que usa un punto inicial para referirse a «este paquete»:

camera_utils/timing.py:

from . import text             # the sibling submodule

def stamp(value):
    return text.label(str(value))

Para incorporar un nombre concreto en su lugar, indícalo tras el hermano con puntos:

from .text import label

def stamp(value):
    return label(str(value))

Las importaciones relativas mantienen un paquete autocontenido: renombrar el directorio del paquete no obliga a editar todos los submódulos.

Usa un paquete cuando un solo archivo supere un tamaño cómodo, o cuando un conjunto de módulos relacionados deba agruparse bajo un mismo espacio de nombres. Para los scripts cotidianos, un único archivo .py es suficiente.

2.19.3. La protección __name__

Todo módulo tiene un nombre integrado __name__. Su valor depende de cómo se esté usando el archivo:

  • Cuando el archivo se ejecuta directamente, __name__ toma el valor de la cadena "__main__".

  • Cuando el archivo es importado por otro script, __name__ toma el valor del nombre del módulo: el nombre de archivo sin .py.

El modismo que aprovecha esto es:

label_util.py:

def label(text):
    return "[" + text + "]"

if __name__ == "__main__":
    print(label("self-test"))

Salida (al ejecutarse directamente):

[self-test]

Cuando el mismo archivo se importa en cambio, __name__ toma el valor del nombre del módulo, por lo que el bloque if se omite y no se ejecuta nada adicional:

>>> import label_util
>>> label_util.__name__
'label_util'

Usa este patrón para añadir una prueba rápida de verificación o una demostración a un archivo de biblioteca sin molestar a los scripts que lo importan.