AulaML Python · C++
Python intermedio · 34 min · Python avanzado

Módulos, paquetes y entornos virtuales

Separar el código en archivos reutilizables y aislar las dependencias de cada proyecto

Un programa pequeño cabe en un archivo; uno real necesita módulos, paquetes y dependencias con versión fija. Aprenderás cómo Python encuentra lo que importas, cómo se ejecuta un módulo una sola vez y cómo crear un entorno virtual que haga tu proyecto reproducible.

Lección 4 de 6 del módulo
Al terminar esta lección podrás
  • Importar módulos y nombres concretos, incluidos alias e imports relativos
  • Explicar el orden de búsqueda de sys.path y el papel de PYTHONPATH
  • Proteger el código de prueba con if __name__ == "__main__"
  • Crear un paquete con __init__.py y una estructura de proyecto clara
  • Crear un entorno virtual, instalar dependencias y fijarlas en requirements.txt

Antes necesitas: Archivos, JSON y CSV · Funciones

En esta lección
  1. ¿Qué es un módulo y qué es un paquete?
  2. ¿Para qué sirve organizar el código así?
  3. ¿Cómo funciona por dentro? La búsqueda y la caché de imports
  4. Sintaxis: import, from y alias
  5. Ejemplo básico: usar la biblioteca estándar
  6. Ejemplo intermedio: tu primer módulo propio
  7. Caso práctico: un paquete con dos módulos
  8. Visualización: qué pasa en un import repetido
  9. Errores comunes
  10. Buenas prácticas
  11. Ejercicios
  12. Soluciones explicadas
  13. Resumen
  14. Evaluación

Hasta ahora tus programas viven en un solo archivo. Eso funciona mientras el código cabe en la pantalla, pero un proyecto real mezcla lectura de datos, limpieza, modelo e informes, y todo en un archivo se vuelve imposible de navegar. Python resuelve el problema con módulos: archivos .py que se importan entre sí.

Hay una segunda pieza que casi nadie enseña al principio: el entorno virtual. Sin él, cada proyecto comparte las mismas bibliotecas instaladas en el sistema, y actualizar una rompe la otra. Juntas, ambas ideas son la diferencia entre un script de práctica y un proyecto que otra persona puede ejecutar dentro de seis meses.

¿Qué es un módulo y qué es un paquete?

Un módulo es un archivo .py cuyo contenido puedes cargar desde otro archivo. Su nombre de módulo es el nombre del archivo sin la extensión: conversiones.py se importa como conversiones. Un paquete es una carpeta de módulos con un archivo __init__.py que marca dónde empieza el paquete; ventas/ con limpieza.py y resumen.py se importa como ventas.

Piensa en un módulo como una caja de herramientas etiquetada: no vuelcas la caja entera sobre la mesa, sacas la herramienta que necesitas. Un paquete es un mueble con cajones: agrupa herramientas por tema y te obliga a decir en qué cajón está cada una. El entorno virtual es el taller propio de cada proyecto, con sus propias herramientas y versiones.

¿Para qué sirve organizar el código así?

  • Reutilizar: la función que normaliza textos se escribe una vez y la usan el informe, la API y las pruebas.
  • Separar responsabilidades: un archivo carga datos, otro los limpia, otro entrena; cada uno se puede leer y probar por separado.
  • Evitar choques de nombres: ventas.limpiar y texto.limpiar pueden convivir porque viven en paquetes distintos.
  • Fijar dependencias: requirements.txt dice exactamente qué versión de NumPy o pytest necesita el proyecto.
  • Colaborar sin sorpresas: quien clona el repositorio crea su entorno virtual, instala y obtiene el mismo resultado.

¿Cómo funciona por dentro? La búsqueda y la caché de imports

Cuando escribes import math, Python no adivina dónde está. Recorre una lista ordenada de rutas, sys.path, y se queda con la primera coincidencia. El orden es: el directorio del script que estás ejecutando (o el directorio actual si usas -c o el REPL), las rutas de la variable de entorno PYTHONPATH, y por último las carpetas de la biblioteca estándar y de los paquetes instalados.

Ese orden explica un error clásico: si tu archivo se llama random.py, Python lo encuentra antes que al random de la biblioteca estándar y tu propio archivo la sustituye. La solución no es pelear con sys.path, es renombrar tu archivo.

Antes de leer el archivo, Python mira sys.modules, un diccionario con todo lo ya importado. Si el módulo está ahí, lo reutiliza y no vuelve a ejecutarlo. Por eso el código de nivel superior corre una sola vez aunque lo importen diez archivos. La primera importación ejecuta el archivo entero, de arriba abajo, y deja un objeto módulo con sus nombres; las siguientes solo enlazan ese objeto.

Cada módulo tiene un atributo __name__. Si lo ejecutas directamente con python3 archivo.py, vale "__main__"; si lo importas desde otro archivo, vale el nombre del módulo ("conversiones"). Esa diferencia permite dejar pruebas al final de un archivo sin que se ejecuten al importarlo, siempre que las encierres en if __name__ == "__main__":.

Sintaxis: import, from y alias

PYTHON Las cuatro formas de importar
# 1. Importa el módulo completo: usas módulo.nombre
import statistics

# 2. Importa un nombre concreto: lo usas directo
from math import sqrt

# 3. Alias para abreviar o evitar choques
import numpy as np
from statistics import mean as promedio

# 4. Dentro de un paquete, import relativo al paquete actual
# from .limpieza import normalizar_sede
# from ..utilidades import formatear

print(statistics.mean([2, 4, 6]))
print(sqrt(81))
print(promedio([10, 20]))

import statistics enlaza el nombre statistics con el módulo; para usar la media necesitas statistics.mean. from statistics import mean trae solo la función, pero pierdes el contexto de dónde venía. Los alias son útiles cuando el nombre es largo (numpy como np) o cuando dos módulos exportan el mismo nombre.

Ejemplo básico: usar la biblioteca estándar

No hace falta instalar nada para empezar: la biblioteca estándar trae matemáticas, estadística, fechas y archivos. Este programa calcula el área de tres círculos y su media.

PYTHON areas.py
import math
from statistics import mean

radios = [1.0, 2.0, 3.0]
areas = [math.pi * r ** 2 for r in radios]

print(round(areas[0], 2))
print(round(mean(areas), 2))

La primera línea trae el módulo math completo; la segunda trae únicamente mean. La comprensión calcula cada área con math.pi, y mean promedia la lista. La salida es 3.14 y 14.66: la media de 3.14, 12.57 y 28.27. Si solo necesitaras una función, from math import pi sería suficiente, pero entonces el nombre pi quedaría suelto y menos claro.

Traer código externo en Python y en C++
Python
PYTHON areas.py
import math

print(math.sqrt(16))
C++
CPP areas.cpp
#include <cmath>
#include <iostream>

int main() {
    std::cout << std::sqrt(16.0) << std::endl;
    return 0;
}

En C++ el `#include` copia el contenido de la cabecera durante la compilación y necesitas `std::`. En Python, `import` carga un objeto módulo en tiempo de ejecución y los nombres viven dentro de él.

Ejemplo intermedio: tu primer módulo propio

Crea una carpeta y dentro dos archivos. El primero, conversiones.py, define constantes y funciones. El segundo, main.py, las usa. Ambos deben estar en la misma carpeta para que la búsqueda de imports los encuentre.

PYTHON conversiones.py
"""Conversiones de unidades usadas en el curso."""

FACTOR_LIBRA_KG = 0.45359237


def libras_a_kilos(libras: float) -> float:
    return libras * FACTOR_LIBRA_KG


def kilos_a_libras(kilos: float) -> float:
    return kilos / FACTOR_LIBRA_KG


if __name__ == "__main__":
    print(libras_a_kilos(150))
PYTHON main.py
import conversiones

peso_lb = 150
print(round(conversiones.libras_a_kilos(peso_lb), 1))

Ejecutar python3 conversiones.py imprime 68.0388555, porque ahí __name__ vale "__main__" y la prueba se ejecuta. Ejecutar python3 main.py imprime 68.0: importar conversiones define las funciones, pero el bloque de prueba queda apagado. Ese es todo el truco del guardián, y es la razón por la que puedes importar sin efectos secundarios.

Caso práctico: un paquete con dos módulos

Cuando un módulo crece, se parte en un paquete. Esta estructura separa la limpieza de textos del cálculo del resumen, y expone una interfaz corta desde __init__.py: quien use el paquete importa ventas y no necesita saber en qué archivo vive cada función.

SHELL Estructura del proyecto
proyecto/
├── main.py
├── requirements.txt
└── ventas/
    ├── __init__.py
    ├── limpieza.py
    └── resumen.py
PYTHON ventas/__init__.py
"""Herramientas para analizar ventas."""

from .limpieza import normalizar_sede
from .resumen import total_por_sede

__all__ = ["normalizar_sede", "total_por_sede"]
PYTHON ventas/resumen.py
from .limpieza import normalizar_sede


def total_por_sede(registros):
    totales = {}
    for sede, monto in registros:
        clave = normalizar_sede(sede)
        totales[clave] = totales.get(clave, 0) + monto
    return totales
PYTHON main.py
from ventas import total_por_sede

registros = [("lima ", 120.0), ("Cusco", 80.5), ("LIMA", 60.0)]
print(total_por_sede(registros))

El punto de from .limpieza significa «dentro de este mismo paquete». La salida es {'Lima': 180.0, 'Cusco': 80.5}: lima y LIMA se normalizan a la misma clave y sus montos se suman. __all__ no bloquea imports, pero documenta qué nombres forman la interfaz pública y es lo que usa from ventas import *.

Crea un entorno virtual antes de instalar nada. En macOS o Linux, python3 -m venv .venv crea la carpeta, source .venv/bin/activate lo activa y el prompt muestra (.venv). En Windows la activación es .venv\Scripts\activate. Después, pip install numpy instala dentro del entorno, no en el sistema.

ComandoQué hace
python3 -m venv .venvCrea un entorno aislado en la carpeta .venv
source .venv/bin/activateActiva el entorno en macOS y Linux
pip install numpyInstala en el entorno activo, no en todo el sistema
pip freeze > requirements.txtEscribe las versiones exactas instaladas
pip install -r requirements.txtReproduce esas versiones en otra máquina

Visualización: qué pasa en un import repetido

Avanza paso a paso. Observa que el segundo import math no vuelve a ejecutar el módulo y que __name__ vale "__main__" porque el archivo se está ejecutando, no importando.

Ejecución visual ·sys.modules evita trabajo repetido
1 / 6
1 import sys
2 import math
3 import math
4
5 print(math.sqrt(81))
6 print("math" in sys.modules)
7 print(__name__)
sys es un módulo integrado: ya está cargado, así que solo se enlaza el nombre.
Variables
sys = <module 'sys' (built-in)>
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

Errores comunes

MensajePor qué ocurreCómo se arregla
ModuleNotFoundError: No module named 'requests'El paquete no está instalado en el entorno activoActiva el entorno y ejecuta pip install requests
ImportError: cannot import name 'media' from 'statistics'El nombre no existe o está mal escritoRevisa la ortografía; en la biblioteca estándar es mean
ImportError: attempted relative import with no known parent packageEjecutaste un módulo del paquete directamente con python3 ventas/resumen.pyEjecuta python3 -m ventas.resumen desde la raíz del proyecto
ModuleNotFoundError: No module named 'ventas'Estás en otra carpeta y la raíz del proyecto no está en sys.pathEjecuta desde la raíz o añade la ruta con PYTHONPATH=.
Ejecutar un módulo del paquete de la forma correcta
Incorrecto
PYTHON Desde dentro de la carpeta del paquete
cd ventas
python3 resumen.py
# ImportError: attempted relative import with no known parent package
Correcto
PYTHON Como módulo, desde la raíz
python3 -m ventas.resumen
# Python conoce el paquete y resuelve el punto

El import relativo necesita saber a qué paquete pertenece el archivo. Al ejecutar el archivo suelto, Python lo trata como `__main__` y pierde ese contexto; con `-m` lo carga como `ventas.resumen` y el punto se resuelve.

Buenas prácticas

  • No nombres un archivo como un módulo de la biblioteca estándar: random.py, json.py o types.py te van a traicionar.
  • Importa arriba del archivo y en grupos: biblioteca estándar, terceros, módulos propios.
  • Usa from x import y cuando el nombre sea inequívoco; si no, prefiere import x para conservar el contexto.
  • Deja en __init__.py solo la interfaz pública y los imports cortos; la lógica va en los módulos internos.
  • Ejecuta los scripts de un paquete con python3 -m paquete.modulo, no con la ruta del archivo.
  • Fija versiones en requirements.txt y mantén .venv fuera del control de versiones.

Ejercicios

Ejercicio 1

Un módulo con guardián

Crea temperaturas.py con dos funciones: c_a_f(celsius) y f_a_c(fahrenheit). Añade un bloque if __name__ == "__main__": que imprima la conversión de 25 °C y de 212 °F. Ejecuta el archivo y después impórtalo desde otro archivo: comprueba que al importarlo no se imprime nada.
Pista

El guardián compara __name__ con la cadena "__main__". Al importar, __name__ es "temperaturas" y el bloque se salta.

Ejercicio 2

Convertir un script en módulo reutilizable

Tienes un script que calcula estadísticas de ventas e imprime todo al ejecutarse. Sepáralo: crea analisis.py con una función resumen(ventas) que devuelva un diccionario con n, media (redondeada a 2 decimales) y maximo. Deja la impresión dentro del guardián. Después escribe informe.py que haga from analisis import resumen e imprima el resultado de una lista de prueba.
Pista

Dentro de resumen usa len, statistics.mean y max. La función devuelve datos; solo el bloque __main__ imprime.

Completar código Completa los imports
import  as st
from math import 

datos = [2, 4, 6]
print((datos))
print(sqrt(81))

El alias `st` se usa como prefijo. La función que falta es la raíz cuadrada.

Soluciones explicadas

En el primer ejercicio, la tentación es dejar las impresiones al final del archivo sin guardián. Funciona mientras ejecutas ese archivo, pero al importarlo desde otro se imprime basura en medio del programa. El guardián convierte el archivo en dos cosas a la vez: script ejecutable por sí mismo y módulo silencioso para los demás.

En el segundo, el cambio importante no es estético. Una función que imprime no se puede probar sin capturar la salida, y no se puede reutilizar para generar un PDF o una respuesta HTTP. Una función que devuelve un diccionario sí. La regla general es: las funciones calculan y devuelven; el código de nivel superior, o el guardián, decide qué hacer con el resultado.

Para reproducir el proyecto en otra máquina, el orden es siempre el mismo: python3 -m venv .venv, activar, pip install -r requirements.txt. Si el archivo no existe todavía, pip freeze > requirements.txt lo genera después de instalar. Fijar versiones evita que una actualización silenciosa cambie los resultados de tu modelo.

Resumen

  • Un módulo es un archivo .py; un paquete es una carpeta con __init__.py.
  • import x trae el módulo; from x import y trae un nombre; as renombra.
  • Python busca en sys.path en orden y reutiliza lo que ya está en sys.modules.
  • Un módulo se ejecuta una sola vez, en su primera importación.
  • __name__ vale "__main__" en el archivo ejecutado y el nombre del módulo al importarlo.
  • Los imports relativos (from .modulo import x) requieren ejecutar con python3 -m paquete.modulo.
  • Un entorno virtual aísla dependencias; requirements.txt las reproduce con versiones fijas.

Evaluación

Evaluación 1

Importas el mismo módulo en dos archivos distintos del programa. ¿Qué ocurre?

Evaluación 2

¿Qué imprime print(__name__) cuando ejecutas python3 conversiones.py directamente?

Evaluación 3

¿Qué problema resuelve un entorno virtual?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.