Módulos y paquetes: organiza tu código sin perderte
Aprende a organizar proyectos en Python usando módulos y paquetes. Entenderás cómo funciona import, para qué sirve __init__.py, cómo separar responsabilidades y cómo estructurar código que pueda crecer sin volverse difícil de …
Cuando un archivo ya no alcanza
Nuestro programa de gastos funciona. Puede registrar compras, calcular totales y guardar información en un archivo.
Pero con cada nueva función aparece un problema que no tiene nada que ver con errores de Python: cada vez cuesta más encontrar dónde está cada cosa.
El programa sigue funcionando, pero para cambiar una parte empezamos a recorrer código que pertenece a problemas completamente diferentes.
¿Podemos hacer que cada parte del programa tenga un lugar donde resulte natural buscarla?
# archivo: programa.py
import csv
from pathlib import Path
ARCHIVO = Path("gastos.csv")
def agregar_gasto(
gastos,
fecha,
categoria,
valor
):
gastos.append({
"fecha": fecha,
"categoria": categoria,
"valor": valor
})
def calcular_total(gastos):
return sum(
gasto["valor"]
for gasto in gastos
)
def guardar_gastos(gastos):
with open(
ARCHIVO,
"w",
newline="",
encoding="utf-8"
) as archivo:
escritor = csv.DictWriter(
archivo,
fieldnames=[
"fecha",
"categoria",
"valor"
]
)
escritor.writeheader()
escritor.writerows(gastos)
def mostrar_resumen(gastos):
total = calcular_total(gastos)
print(
"Total gastado:",
total
)
gastos = []
agregar_gasto(
gastos,
"2026-09-02",
"Comida",
18000
)
agregar_gasto(
gastos,
"2026-09-02",
"Transporte",
6500
)
guardar_gastos(gastos)
mostrar_resumen(gastos)
El código anterior no está roto. Para un programa pequeño puede ser perfectamente razonable mantenerlo todo en un solo archivo.
Lo importante es observar que dentro de programa.py ya conviven tareas que responden a preguntas diferentes.
Crear y modificar los datos con los que trabaja el programa.
Obtener totales y producir información a partir de los gastos.
Convertir la información a CSV y conservarla fuera del programa.
Decidir qué acciones realiza el programa y en qué orden.
El problema aparece cuando responsabilidades diferentes empiezan a crecer juntas y encontrar, reutilizar o modificar una de ellas exige entender demasiadas otras.
No existe una cifra mágica de líneas a partir de la cual debamos crear otro archivo.
Dividiremos el programa cuando podamos identificar partes que tenga sentido entender, mantener o reutilizar por separado.
Organizar código no consiste en repartir líneas entre archivos. Consiste en decidir qué cosas pertenecen juntas.
Tu primer módulo
La imagen anterior nos mostró la idea visualmente: en lugar de dejar todas las responsabilidades dentro de programa.py, podemos empezar a darles un lugar propio.
Comencemos con una de las responsabilidades más claras: trabajar con los gastos.
Un módulo es, en esencia, un archivo .py que contiene código Python que podemos utilizar desde otros archivos.
gastos.py es simplemente otro archivo Python. La diferencia es que ahora tendrá una responsabilidad más concreta.
Extraeremos dos funciones que trabajan sobre los mismos datos: agregar un gasto y calcular el total.
Las funciones siguen haciendo exactamente lo mismo. Solo estamos cambiando dónde viven.
# archivo: gastos.py
def agregar_gasto(
gastos,
fecha,
categoria,
valor
):
gastos.append({
"fecha": fecha,
"categoria": categoria,
"valor": valor
})
def calcular_total(gastos):
return sum(
gasto["valor"]
for gasto in gastos
)
Haber creado gastos.py no hace que main.py conozca automáticamente lo que existe dentro.
¿Cómo puede main.py utilizar una función que ahora vive en otro archivo?
# archivo: main.py
import gastos
mis_gastos = []
gastos.agregar_gasto(
mis_gastos,
"2026-09-02",
"Comida",
18000
)
total = gastos.calcular_total(
mis_gastos
)
print(total)
Cómo funciona import
La instrucción import gastos permite que main.py utilice el módulo que acabamos de crear.
Después del import, podemos acceder a nombres definidos dentro de ese módulo.
gastos.calcular_total(...)
gastos.calcular_total también nos dice de dónde viene la función
Es el módulo que importamos.
Es un nombre definido dentro de ese módulo.
Cuando leemos gastos.calcular_total(), podemos reconocer inmediatamente de dónde proviene esa función.
Las siguientes formas pueden llevarnos al mismo código. Lo que cambia es cómo escribiremos después el nombre que queremos utilizar.
Conserva visible el nombre del módulo.
Importa directamente un nombre concreto.
Crea un alias para referirnos al módulo.
# archivo: main.py
# Tres alternativas de importación.
# Opción 1
import gastos
total = gastos.calcular_total(
mis_gastos
)
# Opción 2
from gastos import calcular_total
total = calcular_total(
mis_gastos
)
# Opción 3
import gastos as g
total = g.calcular_total(
mis_gastos
)
import gastos
gastos.calcular_total()
El origen de la función queda muy visible.
from gastos import calcular_total
calcular_total()
Es más directo cuando necesitamos nombres concretos.
import gastos as g
g.calcular_total()
Utilizamos un nombre alternativo para el módulo.
No estamos compitiendo por escribir menos caracteres. Queremos que otro lector —o nosotros mismos mañana— entienda de dónde viene cada cosa.
from gastos import *?
El asterisco pide importar muchos nombres del módulo sin escribirlos explícitamente.
Cuando el proyecto crece, podemos encontrarnos con calcular_total() sin saber fácilmente si vino de gastos, reportes u otro módulo.
Ocultar el origen de los nombres puede ahorrar escritura, pero también puede quitar claridad.
# archivo: main.py
# Esta forma funciona, pero la evitaremos.
from gastos import *
total = calcular_total(
mis_gastos
)
En las guías anteriores ya escribimos imports como csv y json.
csv
json
math
gastos
La diferencia es que gastos lo creamos nosotros.
Cuando utilizábamos import csv o import json, ya estábamos trabajando con el sistema de módulos de Python.
gastos.py?
En nuestro ejemplo, main.py y gastos.py forman parte del mismo proyecto y están juntos.
encuentra
import gastos
Python tiene reglas y rutas específicas para buscar módulos, pero no necesitamos estudiarlas en profundidad todavía.
Lo importante es entender que nuestros módulos deben encontrarse en una estructura desde la que Python pueda importarlos.
Separar responsabilidades funcionó. Ahora cada parte importante puede tener su propio módulo.
Pero imaginemos que seguimos mejorando nuestro registro de gastos.
gastos.py
archivos.py
reportes.py
validaciones.py
categorias.py
main.py
Las responsabilidades ya están separadas, pero varios de estos módulos pertenecen claramente al mismo contexto financiero.
Si gastos.py, archivos.py y reportes.py están relacionados, ¿podemos expresar también esa relación en la estructura del proyecto?
De módulos a paquetes
La segunda imagen nos mostró el siguiente nivel de organización: varios módulos relacionados pueden vivir dentro de una misma estructura.
En nuestro programa, gastos.py, archivos.py y reportes.py pertenecen al mismo contexto: las finanzas.
Un paquete es una forma de organizar módulos relacionados dentro de una estructura de directorios que Python puede importar.
El paquete que agrupa módulos relacionados.
Un módulo que vive dentro del paquete.
Una función definida dentro del módulo.
finanzas.gastos.agregar_gasto puede leerse como paquete → módulo → nombre definido dentro del módulo.
# archivo: main.py
from finanzas import gastos
mis_gastos = []
gastos.agregar_gasto(
mis_gastos,
"2026-09-02",
"Comida",
18000
)
print(
gastos.calcular_total(
mis_gastos
)
)
Si solamente necesitamos un nombre concreto, podemos expresar todo el camino desde el paquete hasta la función.
from finanzas.gastos import calcular_total indica exactamente de qué paquete y módulo queremos obtener la función.
# archivo: main.py
from finanzas.gastos import calcular_total
mis_gastos = [
{
"fecha": "2026-09-02",
"categoria": "Comida",
"valor": 18000
}
]
total = calcular_total(
mis_gastos
)
print(total)
__init__.py y la estructura del paquete
Dentro de finanzas/ aparece un archivo con un nombre particular: __init__.py.
En los paquetes tradicionales de Python este archivo forma parte de la estructura del paquete y también puede utilizarse para ejecutar inicialización o exponer determinados nombres.
En nuestro proyecto no necesitamos que haga nada especial.
# archivo: finanzas/__init__.py
# Por ahora puede estar vacío.
__init__.py no es obligatorio en absolutamente todos los paquetes modernos
Python también permite paquetes de espacio de nombres que pueden existir sin este archivo.
Utilizaremos __init__.py porque hace explícita nuestra estructura y es una forma clara de aprender cómo se organizan los paquetes tradicionales.
No necesitas memorizar los casos avanzados. Basta con reconocer __init__.py y saber que puede comenzar vacío.
finanzas/gastos.py
Crear y modificar gastos.
finanzas/archivos.py
Guardar información.
finanzas/reportes.py
Calcular y resumir.
main.py
Coordinar el programa.
Si puedes leer el nombre de un módulo y anticipar qué clase de código encontrarás dentro, la estructura está ayudando a comunicar intención.
main.py debería contar la historia del programa
Cuando los detalles viven en módulos especializados, el archivo principal puede concentrarse en decidir qué ocurre y en qué orden.
agregar_gasto()
Registramos información.
guardar_gastos()
La conservamos.
calcular_total()
Generamos un resultado.
main.py debería contar la historia del programa, no contener todos sus detalles.
Hasta ahora hemos colocado principalmente funciones dentro de nuestros módulos. Pero imaginemos que un archivo contiene además una instrucción directamente en su nivel superior.
Python ejecuta el código de nivel superior necesario para inicializar el módulo cuando este se carga.
# archivo: calculadora.py
print("Iniciando calculadora...")
def sumar(a, b):
return a + b
# archivo: main.py
import calculadora
Comentarios y valoraciones
No hay comentarios aún. ¡Sé el primero en opinar!