Python

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 …

Contenido de la guía
El problema

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.

programa.py
Agregar gastos una responsabilidad
Guardar CSV otra responsabilidad
Calcular totales otra responsabilidad
Mostrar reportes otra responsabilidad
Ejecutar el programa coordinación
El síntoma

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?

Python
# 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)
Mira la estructura
El problema no es que el archivo tenga muchas líneas

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.

01 Registrar gastos

Crear y modificar los datos con los que trabaja el programa.

02 Calcular

Obtener totales y producir información a partir de los gastos.

03 Guardar

Convertir la información a CSV y conservarla fuera del programa.

04 Coordinar

Decidir qué acciones realiza el programa y en qué orden.

Tener todo en un archivo no es automáticamente un error.

El problema aparece cuando responsabilidades diferentes empiezan a crecer juntas y encontrar, reutilizar o modificar una de ellas exige entender demasiadas otras.

El cambio de perspectiva
No queremos partir el archivo porque sea grande

No existe una cifra mágica de líneas a partir de la cual debamos crear otro archivo.

Programa pequeño todo puede vivir junto
El programa crece aparecen responsabilidades
Organizamos cada responsabilidad encuentra su lugar
La señal correcta

Dividiremos el programa cuando podamos identificar partes que tenga sentido entender, mantener o reutilizar por separado.

Idea central

Organizar código no consiste en repartir líneas entre archivos. Consiste en decidir qué cosas pertenecen juntas.

Programa grande dividido en varios módulos de Python según sus responsabilidades
Dividir el código por responsabilidades permite entender, reutilizar y mantener cada parte por separado.
Primera separación

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.

registro_gastos/
main.py gastos.py
Módulo

Un módulo es, en esencia, un archivo .py que contiene código Python que podemos utilizar desde otros archivos.

Nada extraño ocurrió

gastos.py es simplemente otro archivo Python. La diferencia es que ahora tendrá una responsabilidad más concreta.

Primera mudanza
Movamos la lógica relacionada con los gastos

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.

Python
# 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
    )
Ahora tenemos dos archivos
Las funciones ya tienen una casa, pero aparece una nueva pregunta
gastos.py contiene la lógica
agregar_gasto() calcular_total()

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?

Python
# 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)
Conectar las piezas

Cómo funciona import

La instrucción import gastos permite que main.py utilice el módulo que acabamos de crear.

main.py import gastos
gastos.py módulo disponible

Después del import, podemos acceder a nombres definidos dentro de ese módulo.

Lee esta expresión

gastos.calcular_total(...)

El punto tiene significado
gastos.calcular_total también nos dice de dónde viene la función
gastos

Es el módulo que importamos.

calcular_total

Es un nombre definido dentro de ese módulo.

Claridad

Cuando leemos gastos.calcular_total(), podemos reconocer inmediatamente de dónde proviene esa función.

Distintas formas
Python permite importar de varias maneras

Las siguientes formas pueden llevarnos al mismo código. Lo que cambia es cómo escribiremos después el nombre que queremos utilizar.

import gastos

Conserva visible el nombre del módulo.

from gastos import calcular_total

Importa directamente un nombre concreto.

import gastos as g

Crea un alias para referirnos al módulo.

Texto plano
# 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
)
Comparemos
No siempre el código más corto es el más claro
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.
La prioridad

No estamos compitiendo por escribir menos caracteres. Queremos que otro lector —o nosotros mismos mañana— entienda de dónde viene cada cosa.

Una forma que evitaremos
¿Por qué no utilizar from gastos import *?

El asterisco pide importar muchos nombres del módulo sin escribirlos explícitamente.

El problema

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.

Python
# archivo: main.py
# Esta forma funciona, pero la evitaremos.

from gastos import *

total = calcular_total(
    mis_gastos
)
Ya conocías esta idea
No es la primera vez que utilizamos módulos

En las guías anteriores ya escribimos imports como csv y json.

csv
json
math
gastos

La diferencia es que gastos lo creamos nosotros.

La conexión

Cuando utilizábamos import csv o import json, ya estábamos trabajando con el sistema de módulos de Python.

Una pregunta natural
¿Cómo sabe Python dónde está gastos.py?

En nuestro ejemplo, main.py y gastos.py forman parte del mismo proyecto y están juntos.

registro_gastos/
main.py gastos.py

Python tiene reglas y rutas específicas para buscar módulos, pero no necesitamos estudiarlas en profundidad todavía.

Por ahora

Lo importante es entender que nuestros módulos deben encontrarse en una estructura desde la que Python pueda importarlos.

El proyecto sigue creciendo
Resolver un problema nos lleva al siguiente

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
Seguimos estando mejor que al principio.

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?

Varios módulos de Python agrupados dentro de un paquete
Un paquete permite organizar módulos relacionados dentro de una misma estructura.
Agrupar lo relacionado

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.

Paquete

Un paquete es una forma de organizar módulos relacionados dentro de una estructura de directorios que Python puede importar.

registro_gastos/
main.py finanzas/ __init__.py gastos.py archivos.py reportes.py
Tres niveles
Paquete, módulo y función describen cosas diferentes
finanzas

El paquete que agrupa módulos relacionados.

gastos.py

Un módulo que vive dentro del paquete.

agregar_gasto()

Una función definida dentro del módulo.

Lee la jerarquía

finanzas.gastos.agregar_gasto puede leerse como paquete → módulo → nombre definido dentro del módulo.

Python
# 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
    )
)
Un camino más preciso
También podemos llegar directamente a una función

Si solamente necesitamos un nombre concreto, podemos expresar todo el camino desde el paquete hasta la función.

finanzas paquete
gastos módulo
El import describe una ubicación

from finanzas.gastos import calcular_total indica exactamente de qué paquete y módulo queremos obtener la función.

Python
# 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)
Un nombre extraño

__init__.py y la estructura del paquete

Dentro de finanzas/ aparece un archivo con un nombre particular: __init__.py.

__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.

Texto plano
# archivo: finanzas/__init__.py

# Por ahora puede estar vacío.
Una precisión
__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.

Para nuestro proyecto

Utilizaremos __init__.py porque hace explícita nuestra estructura y es una forma clara de aprender cómo se organizan los paquetes tradicionales.

Lo necesario ahora

No necesitas memorizar los casos avanzados. Basta con reconocer __init__.py y saber que puede comenzar vacío.

Cada responsabilidad tiene dirección
El árbol del proyecto empieza a explicar el programa
finanzas/gastos.py

Crear y modificar gastos.

finanzas/archivos.py

Guardar información.

finanzas/reportes.py

Calcular y resumir.

main.py

Coordinar el programa.

Una buena señal

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.

El coordinador
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.

Algo inesperado
Importar un módulo puede ejecutar código

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.

Al importar

Python ejecuta el código de nivel superior necesario para inicializar el módulo cuando este se carga.

Python
# archivo: calculadora.py

print("Iniciando calculadora...")


def sumar(a, b):
    return a + b
Python
# archivo: main.py

import calculadora

Estás viendo solo el 60% del contenido. Hazte Premium para acceder a la guía completa.

Comunidad

Comentarios y valoraciones

No hay comentarios aún. ¡Sé el primero en opinar!