6. Módulos y Paquetes#
La descomposición de un programa en componentes independientes, no solo nos permite dividir y en cierto modo reducir la complejidad de nuestro sistema. También nos permite definir una frontera física sobre el código que implementa cada componente creando una unidad básica de gestión. De tal manera que el módulo puede estar protegido para que solo lo editen los programadores responsables y también se pueden controlar más facilmente las versiones y cambios que se realicen en el módulo.
En Python un módulo se utiliza como la unidad física básica de
descomposición, ya que un módulo en Python es simplemente un archivo con
extensión .py. En este archivo programamos la definición de los objetos que
componen el módulo los cuales queremos reutilizar en otros componentes o
programas.
Un módulo también puede ejecutarse como si fuera un script independiente e incluye instrucciones que se ejecutan en el momento de la importación o cuando el módulo se ejecuta directamente como script.
Veamos un ejemplo. Vamos a suponer que sumar (y restar) dos números es una
operación muy complicada que además utilizamos en varias partes de nuestro
programa. Decidimos crear un módulo dónde se incluyan estas dos funciones pues
consideramos que son parte del mismo problema y tienen mucha relación.
Definimos entonces el módulo aritmética.py con este código:
aritmética.py.#pi = 3.141592653589793 # Variable global
def suma(a: int, b:int) -> int:
return a + b
def resta(a: int, b:int) -> int:
return suma(a,-b)
Ahora podemos utilizar este módulo ejecutando el intérprete en la misma ruta dónde está nuestro programa.
Python 3.13.2 (tags/v3.13.2:4f8bb39, Feb 4 2025, 15:23:48) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import aritmética
>>> aritmética.suma(5,8)
13
>>> aritmética.resta(5,8)
-3
Todos los módulos incluyen una variable global llamada __name__:
>>> import aritmética
>>> aritmética.__name__
'aritmética'
>>> __name__
'__main__'
Como vemos, en caso de que módulo se haya ejecutado como un script
este tendrá el nombre de '__main__'. Esto lo podemos utilizar
para ejecutar de manera selectiva código que queremos que se
ejecute en caso de que el módulo se ejecute como script:
aritmética.py.#pi = 3.141592653589793 # Variable global
def suma(a: int, b:int) -> int:
return a + b
def resta(a: int, b:int) -> int:
return suma(a,-b)
if __name__ == "__main__":
import sys
suma(int(sys.argv[1]), int(sys.argv[2]))
Ahora ejecutemos el módulo aritmetica.py como script:
$ python .\aritmética.py 12 900
912
Los módulos contienen variables globales, que siguiendo los principios del encapsulamiento, solo deberían ser para uso privado del módulo. Sin embargo, es la tendencia en Python, si sabemos lo que estamos haciendo, podemos modificar y leer estas variables desde código externo al módulo.
Nota
Recordemos que en Python, por convención, los nombres que comienzan con un
guion bajo (por ejemplo _pi) indican que son parte de la implementación
interna del módulo y no deberían utilizarse desde código externo.
>>> import aritmética
>>> print(aritmética.pi)
3.141592653589793
>>> aritmética.pi = 3.1416
>>> print(aritmética.pi)
3.1416
6.1. Otras variantes de import#
Hay distintas variantes de la instrucción import, que afectan
la manera en la que se incluyen en el módulo actual los elementos importados.
Podemos incluir directamente en el espacio de nombres algunos de los objetos definidos en el módulo externo, en esta versión el módulo no se incluye al espacio de nombres:
>>> from aritmética import suma, resta
>>> aritmética # No está definido localmente
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
aritmética
NameError: name 'aritmética' is not defined
>>> suma(2,3) # Se agrega la función 'suma()' al espacio de nombres local
5
Podemos importar todos las definiciones con *. Esto no es recomendable ya
que se podría ocultar o redefinir cierto nombre del espacio local y esta forma
ignora explícitamente los límites del espacio de nombres y dificulta el
análisis estático del código.
>>> from aritmética import *
>>> pi
3.141592653589793
Podemos darle un alias local al nombre del módulo:
>>> import aritmética as m
>>> m.pi
3.141592653589793
Por último, podemos darle un alias a los nombres importados, de nuevo, tenemos que tener cuidad de no acultar algún nombre:
>>> sum([1,2,3,4]) # Utilizamos la función 'built-in' sum()
10
>>> from aritmética import suma as sum
>>> sum([1,2,3,4]) # Redefinimos así que ya no funciona
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
sum([1,2,3,4])
~~~^^^^^^^^^^^
TypeError: suma() missing 1 required positional argument: 'b'
>>> sum(2,3) # Cambió a sumar dos números.
5
6.2. Encontrando al Módulo importado#
Como hemos visto, un módulo no es más que un archivo con extensión .py.
Cuando ejecutamos la instrucción import el intérprete de Python inicia
una búsqueda para encontrar el archivo o módulo que especificamos. El primer
lugar dónde se busca es en los módulos built-in, estos nombres se encuentran
en la tupla sys.builtin_module_names. Incluye módulos que se utilizan
bastante como array, math, sys, time, itertools. Al no encontrar el
nombre del módulo, busca un archivo en los directorios incluidos en la variable
sys.path. Esta lista se incializa a partir de los siguientes datos:
El directorio dónde se encuentra el script desde dónde se llamó el
import. En caso de que no se esté corriendo un script, se busca en el directorio actual.En las rutas especificadas en la variable de entorno
PYTHONPATH. De manera similar aPATH.El valor por defecto especificado al momento de la instalación. Es común buscar en el directorio
site-packages.
Es posible modificar la variable sys.path en tiempo de ejecución.
6.3. Paquetes#
Hemos visto que los espacios de nombres en Python se definen de manera dinámica
e impicitamente al definir objetos como: módulos, clases y funciones. Podemos
incluso verificar los nombres en el ámbit actual con funciones como como
globals(), locals(), vars() o dir(). En
otros lenguajes el espacio de nombres se declara explícitamente con bloques, por
ejemplo, en C# se tiene la palabra reservada namespace nombre { }. Estos
espacios son estáticos y también tienen el propósito de evitar conflictos de
nombres y sirven para organizar el código. Normalmente los lenguajes utilizan
una notación de punto para referirse a los subcomponentes en una relación
jerárquica de composición. Veamos un ejemplo en C#:
namespace Utilerías {
class Calculadora {
public static int Sumar(int a, int b) => a + b;
}
}
Y el uso en un programa:
using Utilerías;
int resultado = Calculadora.Sumar(3, 4);
En el caso de Python se utiliza la misma estructura jerárquica del sistema de archivos basada en directorios y subdirectorios. Por ejemplo, la librería de procesamiento de audio librosa tiene una estructura similar a esta:
librosa
│ beat.py
│ display.py
│ effects.py
│ filters.py
│ segment.py
│ sequence.py
│ version.py
│ _cache.py
│ _typing.py
│ __init__.py
├───core
│ │ audio.py
│ │ fft.py
│ │ harmonic.py
│ │ pitch.py
│ │ spectrum.py
│ └── __init__.py
├───feature
│ │ rhythm.py
│ │ spectral.py
│ │ utils.py
│ └── __init__.py
└───util
│ decorators.py
│ deprecation.py
│ exceptions.py
│ matching.py
│ utils.py
│ _nnls.py
│ __init__.py
└───example_data
│ index.json
│ registry.txt
└── __init__.py
Esta estructura de archivos define la jerarquía de composición del paquete librosa. Cada
directorio es un paquete que contiene una colección de módulos y posiblemente otros supaquetes.
En este caso se tienen tres subpaquetes:
coreDónde se incluye la funcionalidad principal de I/O y DSP (Digital Signal Processing) estas funciones nos permiten cargar, procesar y generar señales de audio, así como su representación en distintos formatos.
featureContiene los módulos especializados en extraer características del audio (MFCC, cromas, espectrogramas, tempo, tonalidad, etc.).
utilSe incluyen aquí módulos auxiliares para el procesamiento, como operaciones enArrays, archivos y validaciones.
Esta organización permite a los desarrolladores entender mejor el código y dividir mejor la colaboración al ser un proyecto de código abierto.
Los directorios (paquetes para Python) incluyen un archivo llamado __init__.py el
cual identifica al directorio como paquete y además puede contener código en
para incializar el paquete o definir la lista __all__ que contiene los
nombres de los módulos que se van incluir cuando los usuarios del paquete lo importen con
utilizando un *. La definición de esta lista es opcional.
En el módulo de audio librosa.core.audio se tiene el método de load()
que sirve para cargar un archivo de audio. Podríamos cargar el método utilizando
import como vimos en la sección anterior (asumiendo que tenemos instalada la librería):
>>> from librosa.core.audio import load
>>> load
<function load at 0x0000021679668C20>
Sin embargo en la documentación, se recomienda utilizar load() de la
siguiente manera:
import librosa
filename = librosa.example('nutcracker')
y, sr = librosa.load(filename)
Del ejemplo vemos que solo necesitamos importar la librería y ya. La función
load() parece estar disponible de manera mágica, pero en realidad se
utiliza internamente la librería lazy_loader para cargar subomodelos y
funciones de manera lazy, solo se incluyen hasta el momento en el que se
utilizan y solo se cargan la primera vez. Esta librería se utiliza al nivel de
los archivos __intit__.py que vimos anteriormente.
Nota
Esta funcionalidad es posible porque en Python los especios de nombres son dinámicos y se resulven en tiempo de ejecución.
Dentro de un paquete se puede hacer referencia a otros subpaquetes utilizando
la notación de ruta relativa utilizada en el sistema de archivos. Por ejemplo,
desde el módulo de librosa.core.audio nos podemos referir a
librosa.utils.util de distintas maneras:
from .fft import get_fftlib # Módulo en el mismo directorio
from ..util.exceptions import ParameterError
Resumen del capítulo#
En este capítulo estudiamos cómo Python organiza programas complejos mediante módulos y paquetes, que constituyen la unidad fundamental de reutilización, encapsulamiento y distribución del código.
Vimos que un módulo es simplemente un archivo .py que define funciones,
clases y variables, y que puede utilizarse tanto como biblioteca reutilizable
como script ejecutable. Analizamos el papel de la variable especial
__name__ para distinguir entre ambos usos, así como las distintas variantes
de la instrucción import y su impacto en el espacio de nombres.
También revisamos el proceso mediante el cual Python localiza los módulos
importados, destacando el papel de sys.path y la diferencia entre módulos
integrados, paquetes instalados y código local.
Posteriormente introdujimos el concepto de paquete, mostrando cómo la
estructura de directorios del sistema de archivos define jerarquías lógicas de
composición. A través de un ejemplo real (librosa), observamos cómo los
paquetes permiten organizar proyectos grandes, facilitar la colaboración y
optimizar el tiempo de carga mediante técnicas como lazy loading.
Finalmente, conectamos estos mecanismos con conceptos vistos en capítulos anteriores, como espacios de nombres, encapsulamiento y evaluación diferida.