Saltar a contenido

Módulos

Banner

Imagen generada con Inteligencia Artificial

Escribir pequeños trozos de código puede resultar interesante para realizar determinadas pruebas o funcionalidades sencillas. Pero a la larga, nuestros programas tenderán a crecer y será necesario agrupar el código en piezas/artefactos más manejables.

Módulos

Un módulo en Python es simplemente un fichero de texto que contiene código fuente. Permite evitar la repetición y favorece la reutilización.

Los módulos pueden agruparse en carpetas denominadas paquetes mientras que estas carpetas, a su vez, pueden dar lugar a librerías.

Librerías paquetes y módulos

Un ejemplo de todo ello lo encontramos en la librería estándar. Se trata de una librería que ya viene incorporada en Python y que, a su vez, dispone de una serie de paquetes que incluyen distintos módulos.

Un caso concreto dentro de la stdlib (libería estándar) podría ser el del paquete urllib —para operaciones con URLs— que dispone de 5 módulos:

Paquete urllib

Importar un módulo completo

Para hacer uso del código de otros módulos usaremos la sentencia import. Permite importar el código y las variables de dicho módulo para tenerlas disponibles en nuestro programa.

La forma más sencilla de importar un módulo es import module donde module es el nombre de otro fichero Python, sin la extensión .py.

Partiremos del siguiente ejemplo donse se ha implementado un módulo para cálculos estadísticos:

stats.py
def mean(*values: int | float) -> float:
    """Calculate mean of values"""
    return sum(values) / len(values)


def std(*values: int | float) -> float:
    """Calculate standard deviation of values"""
    m = mean(*values)
    p = sum((v - m) ** 2 for v in values)
    return (p / (len(values) - 1)) ** (1 / 2)

Desde otro fichero (en principio en la misma carpeta) podríamos importar el contenido de stats.py para hacer uso de sus funcionalidades:

>>> import stats

>>> stats.mean(6, 3, 9, 5)#(1)!
5.75
>>> stats.std(6, 3, 9, 5)#(2)!
2.5

  1. Es necesario anteponer a la función mean() el espacio de nombres que define el módulo stats.
  2. Es necesario anteponer a la función std() el espacio de nombres que define el módulo stats.

Librería estándar

En el caso de utilizar un módulo de la librería estándar, basta con saber su nombre e importarlo directamente:

import os

Ruta de búsqueda de módulos

Cuando importamos un módulo en Python el intérprete trata de encontrarlo (por orden) en las rutas definidas dentro de la variable sys.path.

Veamos el contenido de la variable sys.path para el caso concreto de mi entorno de desarrollo:

1
2
3
4
5
6
7
8
9
>>> import sys

>>> sys.path
['/Users/sdelquin/code/personal/aprendepython/.venv/bin',
 '/Users/sdelquin/.local/share/uv/python/cpython-3.13.2-macos-aarch64-none/lib/python313.zip',
 '/Users/sdelquin/.local/share/uv/python/cpython-3.13.2-macos-aarch64-none/lib/python3.13',
 '/Users/sdelquin/.local/share/uv/python/cpython-3.13.2-macos-aarch64-none/lib/python3.13/lib-dynload',
 '',
 '/Users/sdelquin/code/personal/aprendepython/.venv/lib/python3.13/site-packages']
  • L4 Comandos ejecutables del entorno virtual.
  • L5 Contiene módulos de la librería estándar en formato comprimido.
  • L6 Carpeta con módulos adicionales que no pueden estar comprimidos (por ejemplo, archivos compilados en C).
  • L7 Contiene las extensiones compartidas (.so, .pyd) que no pueden ejecutarse desde un ZIP.
  • L8 Representa el directorio (carpeta) actual.
  • L9 Paquetes instalados en el entorno virtual.

Podemos modificar la ruta de búsqueda de paquetes Python. Para ello existen dos opciones:

Modificando directamente la variable de entorno PYTHONPATH:

export PYTHONPATH=/tmp

Comprobamos que se ha modificado en sys.path:

>>> import sys

>>> sys.path
['/Users/sdelquin/code/personal/aprendepython-mkdocs/.venv/bin',
 '/tmp',
 '/Users/sdelquin/.local/share/uv/python/cpython-3.13.2-macos-aarch64-none/lib/python313.zip',
 '/Users/sdelquin/.local/share/uv/python/cpython-3.13.2-macos-aarch64-none/lib/python3.13',
 '/Users/sdelquin/.local/share/uv/python/cpython-3.13.2-macos-aarch64-none/lib/python3.13/lib-dynload',
 '',
 '/Users/sdelquin/code/personal/aprendepython-mkdocs/.venv/lib/python3.13/site-packages']

Para ello accedemos directamente a la lista que está en el módulo sys de la librería estandar:

>>> import sys

>>> sys.path.insert(0, '/tmp')#(1)!
>>> sys.path[0]
'/tmp'

>>> sys.path.append('/tmp')#(2)!
>>> sys.path[-1]
'/tmp'

  1. Insertar por el principio.
  2. Insertar por el final.

Orden de rutas

El hecho de poner nuestra ruta al principio o al final de sys.path influye en la búsqueda, ya que si existen dos (o más módulos) que se llaman igual en nuestra ruta de búsqueda, Python usará el primero que encuentre.

Importar partes de un módulo

Es posible que no necesitemos todo aquello que está definido en stats.py. Por ejemplo sólo vamos a calcular medias. En este caso la importación se hace de manera diferente:

>>> from stats import mean

>>> mean(6, 3, 9, 5)#(1)!
5.75

  1. La función se utiliza sin ningún prefijo.

Este esquema tiene el inconveniente de la posible colisión de nombres, en aquellos casos en los que tuviéramos algún objeto con el mismo nombre que el objeto que estamos importando. Para estas (u otras) situaciones Python permite usar alias a través de la sentencia as:

>>> from stats import mean as avg

>>> avg(6, 3, 9, 5)
5.75

Para importar varios objetos (funciones en este caso) desde un mismo módulo, podemos especificarlos separados por comas en la misma línea:

>>> from stats import mean, std

Es posible hacer from stats import * pero estaríamos importando todos los componentes del módulo, cuando a lo mejor no es lo que necesitamos. A continuación una imagen que define bien este escenario:

Dark image Light image

Paquetes

Un paquete es simplemente una carpeta que contiene ficheros .py. Además permite tener una jerarquía con más de un nivel de subcarpetas anidadas.

Pongamos un ejemplo creando un paquete extramath que contendrá dos módulos:

  • stats.py para cálculos estadísticos.
  • ofrac.py para operaciones auxiliares con fracciones.

El módulo stats.py ya se definió previamente aquí y el código del módulo ofrac.py se presenta a continuación:

ofrac.py
def gcd(a: int, b: int) -> int:
    """Greatest common divisor through Euclides Algorithm"""
    while b > 0:
        a, b = b, a % b
    return a


def lcm(a: int, b: int) -> int:
    """Least common multiple through Euclides Algorithm"""
    return a * b // gcd(a, b)

Si nuestro código principal va a estar en main.py (a primer nivel), la estructura de ficheros nos quedaría tal que así:

.
├── main.py(1)
└── extramath(2)
    ├── ofrac.py(3)
    └── stats.py(4)

1 directory, 3 files
  1. Punto de entrada de nuestro programa a partir del fichero main.py
  2. Carpeta que define el paquete extramath.
  3. Módulo para operaciones auxiliares de fracciones.
  4. Módulo para cálculos estadísticos.

Si lo ponemos todo junto, nos quedaría un esquema como el siguiente:

flowchart LR
    subgraph Package
        E@{ shape: docs, label: "extramath"}
    end
    subgraph Module
        O@{ shape: delay, label: "ofrac"}
        S@{ shape: delay, label: "stats"}
    end
    subgraph Function
        gcd["<tt>gcd()</tt>"]
        lcm["<tt>lcm()</tt>"]
        mean["<tt>mean()</tt>"]
        std["<tt>std()</tt>"]
    end
    E --> O
    E --> S
    O --> gcd
    O --> lcm
    S --> mean
    S --> std

Importar desde un paquete

Si ya estamos en el fichero main.py (o a ese nivel) podremos hacer uso de nuestro paquete de la siguiente forma:

>>> from extramath import frac, stats#(1)!

>>> frac.gcd(21, 35)#(2)!
7

>>> stats.mean(6, 3, 9, 5)#(3)!
5.75

  1. Importar los módulos frac y stats del paquete extramath
  2. Uso de la función gcd que está definida en el módulo frac
  3. Uso de la función mean que está definida en el módulo stats

Programa principal

Cuando decidimos desarrollar un artefacto de software en Python, normalmente usamos distintos ficheros para ello. Algunos de esos ficheros se convertirán en módulos, otros se englobarán en paquetes y existirá uno en concreto que será nuestro punto de entrada, también llamado programa principal.

La anatomía de un programa principal (habitualmente llamado main.py) es la siguiente:

main.py
# imports de la librería estándar
# imports de librerías de terceros
# imports de módulos propios

# CÓDIGO PROPIO
# ...

if __name__ == '__main__':
    # punto de entrada real

Si queremos ejecutar este fichero main.py desde línea de comandos, tendríamos que hacer:

python main.py

Punto de entrada

Tratemos de explicar cuál es el cometido de la sentencia: if __name__ == '__main__'

Esta condición permite, en el programa principal, diferenciar qué codigo se lanzará cuando el fichero se ejecuta directamente o cuando el fichero se importa desde otro lugar.

La variable __name__ puede tomar dos posibles valores:

  • El nombre del módulo (o paquete) al importar el fichero.
  • El valor '__main__' al ejecutar el fichero.

Veamos el siguiente ejemplo con el programa hello.py y analicemos su comportamiento según el escenario escogido:

hello.py
import blabla


def myfunc():
    print('Inside myfunc')
    blabla.hi()


if __name__ == '__main__':
    print('Entry point')
    myfunc()

Dark image Light image

  • import hello


    Se ejecutan las siguientes líneas (desde arriba hacia abajo):

    L1 se importa el módulo blabla.
    L4 se define la función myfunc y estará disponible para usarse.
    L9 esta condición no se cumple, ya que estamos importando y la variable especial __name__ no toma ese valor. Con lo cual finaliza la ejecución.

    No hay salida por pantalla.

  • python main.py


    Se ejecutan las siguientes líneas (desde arriba hacia abajo):

    L1 se importa el módulo blabla.
    L4 se define la función myfunc y estará disponible para usarse.
    L9 esta condición sí se cumple, ya que estamos ejecutando directamente el fichero (como programa principal) y la variable especial __name__ toma el valor '__main__'.
    L10 Entry point
    L11 llamada a la función myfunc().
    L5 Inside myfunc
    L6 llamada a la función hi() del módulo blabla.