Aprende leyendo en orden

__init__.py e imports relativos — Agrupar archivos en un paquete

Aprende el papel de __init__.py para construir paquetes de Python y cuándo usar imports absolutos vs relativos.

El artículo anterior cubrió los módulos de un solo archivo y cómo funciona import. Cuando una aplicación crece, querrás agrupar módulos relacionados bajo una sola carpeta como un paquete. Este artículo recorre cómo importar paquetes.

Un paquete es una carpeta que contiene __init__.py

Un paquete es una carpeta que contiene un archivo especial llamado __init__.py. Cuando Python lo encuentra, la carpeta entera se vuelve un paquete y otro código puede traerlo con import nombre_de_carpeta. __init__.py es lo primero que se ejecuta cuando se importa el paquete, y es un sitio típico para declarar qué funciones y clases expone el paquete.

Anatomía de un paquete
my_package/ (un paquete)
  • __init__.py — el archivo que se ejecuta primero cuando se importa el paquete
  • calculation.py — un módulo (add / multiply)
  • string_utils.py — un módulo (format_name, etc.)
La carpeta my_package/ contiene __init__.py, así que Python trata la carpeta como un paquete. Los módulos internos como calculation.py quedan expuestos hacia fuera a través de __init__.py.

__init__.py puede estar vacío. Incluso un archivo vacío basta — Python solo necesita la presencia del archivo para reconocer la carpeta como paquete.

Importar sin pasar por __init__.py

Incluso con un __init__.py vacío, todavía puedes importar un módulo directamente desde el paquete. La forma es from nombre_de_paquete.nombre_de_módulo import nombre_de_función — conecta la ubicación del archivo con puntos.

# my_package/__init__.py  <- vacío está bien

# my_package/calculation.py
def add(a, b):
    return a + b


# En main.py
from my_package.calculation import add   # se escribe el nombre de archivo `calculation`
print(add(1, 2))   # 3

El inconveniente: los llamadores tienen que saber qué archivo contiene qué. Si reorganizas el paquete después (digamos, divides calculation.py en dos), todos los llamadores tienen que cambiar también. El patrón de la siguiente sección de re-exportar a través de __init__.py lo arregla — permite que los llamadores se refieran a nombres cortos directos del paquete.

Reunir la API pública en __init__.py

Escribe from .nombre_de_módulo import nombre_de_función dentro de __init__.py y la función aparece como si viviera directamente bajo el paquete. Por ejemplo, from .calculation import add, multiply dentro de __init__.py deja que los llamadores escriban from my_package import add, multiply — corto y limpio.

Re-exportar a través de __init__.py para imports más cortos
my_package/main.py__init__.pyfrom .calculationimport add, multiplycalculation.pydef add()def multiply()from my_packageimport addadd(1, 2)invocablevíaeleva
Cuando __init__.py eleva add desde calculation.py, los llamadores pueden escribir from my_package import add sin pensar en qué archivo (calculation) lo contiene.

La carpeta my_package/ está adjunta a la izquierda (abre calculation.py y __init__.py desde 📂 Files para inspeccionarlas).

① Usa from my_package import add, multiply para cargar ambas funciones.

② Imprime los resultados de add(10, 20) y multiply(3, 4).

Editor Python

Ejecutar el código para ver el resultado

Por qué reunir la API pública en __init__.py

Si los llamadores solo tocan lo que __init__.py expone, puedes reorganizar archivos internos después sin tocar el código de los llamadores. Eso es encapsulación clásica a nivel de paquete — el patrón básico del diseño de paquetes.

from vs import — dos formas de escribirlo

Hay dos formas de import: from paquete import nombre y import paquete. Ambas traen el objetivo, pero cómo lo llamas después es distinto.

from vs import — qué cae en el ámbito
from my_pkg.calcimport adden el ámbito:addllamada:add(1, 2)importmy_pkg.calcen el ámbito:my_pkgllamada:my_pkg.calc.add(1, 2)
from paquete import nombre trae el nombre mismo al ámbito para que puedas usarlo directamente. import paquete solo trae el nombre del paquete al ámbito; las llamadas tienen que usar la ruta completa con puntos.
# Forma 1: from ... import ...
from my_package.calculation import add
print(add(1, 2))   # add se puede usar directamente


# Forma 2: import ...
import my_package.calculation
print(my_package.calculation.add(1, 2))   # llamada con la ruta completa con puntos


# Forma 2 + as alias (acorta nombres largos)
import my_package.calculation as calc
print(calc.add(1, 2))
FormaNombre en el ámbitoCómo llamarla
from my_package.calculation import addaddadd(1, 2)
import my_package.calculationmy_packagemy_package.calculation.add(1, 2)
import my_package.calculation as calccalccalc.add(1, 2)

La forma más común es from paquete import nombre — el nombre de la función es corto y el código del lado main se lee limpio. Pero cuando dos paquetes distintos exportan el mismo nombre de función y necesitas ambos, la forma import paquete mantiene visible el nombre del módulo, así queda claro a cuál estás llamando.

from vs import — qué cae en el ámbito de main
from my_pkg.calc import add
  • add — el nombre mismo cae en el ámbito de main
  • Llamada: add(1, 2) funciona directamente
  • Corto de escribir, pero no es obvio de qué paquete vino add
import my_pkg.calc
  • my_pkg — solo el nombre del paquete cae en el ámbito de main
  • Llamada: my_pkg.calc.add(1, 2) con la ruta completa con puntos
  • Más largo de escribir, pero my_pkg.calc.add deja obvio el origen
from paquete import nombre trae el nombre mismo al ámbito de main (= llamadas cortas). import paquete trae solo el nombre del paquete, así que las llamadas usan la ruta completa con puntos (= el origen es explícito).

Prueba ambas formas con el paquete mathlib/ adjunto. calculator.py define triple(n) (verifícalo en 📂 Files).

① Usa la forma from ... import ... para traer triple, luego print(triple(7)).

② Después usa la forma import ... as ... para también traer mathlib.calculator bajo el alias calc, e imprime print(calc.triple(7)).

Editor Python

Ejecutar el código para ver el resultado

Imports absolutos vs relativos

¿Y si los módulos dentro de un paquete necesitan referirse entre sí? Digamos que utility/validator.py quiere importar de utility/helper.py — hay dos formas de escribirlo: import absoluto e import relativo. Para elegir entre ellos, primero necesitas tener claro qué significa la raíz del proyecto.

Qué es la raíz del proyecto
project/ ← la raíz (donde está main.py)
  • main.py ← el punto de entrada que ejecutas primero
  • config.py ← ajustes de toda la app
my_app/
  • __init__.py ← se ejecuta cuando ocurre import my_app
utility/
  • __init__.py
  • validator.py
  • helper.py
La raíz del proyecto es la carpeta donde está main.py (el punto de entrada). El my_app del import absoluto from my_app.utility import validator se refiere a la carpeta del mismo nombre directamente bajo esa raíz.

La raíz del proyecto es la carpeta que contiene el punto de entrada que ejecutas con python main.py. Cuando escribes el import absoluto from my_app.utility import validator, Python busca my_app directamente bajo esa raíz y luego entra en utility/validator.py. La ruta con puntos refleja la estructura de carpetas — eso es todo lo que es un import absoluto.

TipoFormaQué significa
Import absolutofrom my_app.utility import helperRuta completa desde la raíz del proyecto
Import relativofrom . import helperRelativo al archivo en el que estás
Import relativo (padre)from .. import configApunta a un archivo una carpeta arriba
Dos formas de leer helper.py desde validator.py
Absolutofrom my_app.utilityimport helperRuta completadesde la raízRelativofrom . importhelperBasado enla carpeta actual
Cuando lees un módulo vecino en el mismo paquete, tanto el absoluto como el relativo funcionan. El . en from . import significa «la misma carpeta en la que estoy».

El reparto habitual: el punto de entrada (main.py) usa imports absolutos para llegar a los paquetes, y los módulos dentro de un paquete usan imports relativos para referirse entre sí. Con imports relativos, renombrar después una carpeta de paquete no obliga a cambios en el código interno.

# my_app/utility/validator.py
# Import relativo de helper.py en el mismo paquete
from .helper import log_message


def validate_user(user):
    if user.name and user.email:
        log_message("OK")
        return True
    log_message("problema detectado")
    return False


# my_app/utility/helper.py
def log_message(message):
    print("[LOG]", message)


# Para llegar a config.py en otro paquete,
# sube uno con ..:
# from ..config import get_config
Disposición de carpetas para el código de arriba
my_app/
  • config.py ← objetivo de from ..config import ...
utility/
  • validator.py ← escribe from .helper import log_message
  • helper.py ← provee log_message(...)
validator.py y helper.py están lado a lado en la misma carpeta utility/. Por eso from .helper import log_message en validator.py usa . — «esta carpeta (= utility/)». config.py está un nivel arriba en my_app/, así que desde la vista de validator lo alcanzarías como ..config (.. = un nivel arriba).

Los puntos de entrada no pueden usar imports relativos

Un archivo que ejecutas directamente con python main.py no puede usar imports relativos — tendrás un ImportError. Los imports relativos solo funcionan cuando el archivo se carga como miembro de algún paquete. Desde el punto de entrada, usa siempre imports absolutos (por ejemplo, from my_app.utility import validate_user).

Prueba imports absolutos y relativos con el paquete shop/ adjunto. Abre 📂 Files y verás que shop/__init__.py está vacío — ni Cart ni to_yen están aún expuestos en la raíz del paquete.

① Primero, ejecuta main.py tal cual — obtendrás un ImportError porque shop no exporta nada.

② Después, abre shop/__init__.py desde 📂 y escribe from .cart import Cart y from .formatter import to_yen como imports relativos (. = este paquete), y guarda.

③ Añade el uso de Cart en main.py — construye un Cart, añade apple por $100 y orange por $200, después formatea el total con to_yen() e imprímelo. El lado main usa import absoluto, los internos del paquete usan imports relativos — esa es la división.

Editor Python

Ejecutar el código para ver el resultado

Una disposición al estilo de un proyecto real

Con estas reglas puedes montar estructuras de carpetas que casan con apps reales. Aquí tienes una disposición dividida en tres paquetes — settings, database y utility:

Una disposición cercana a un proyecto real
project/ (raíz del proyecto)
  • main.py — punto de entrada (el archivo que ejecutas con python)
  • config.py — ajustes de toda la app
my_app/
  • __init__.py — API pública del paquete my_app
database/
  • __init__.py
  • connection.py / models.py
utility/
  • __init__.py
  • validator.py / helper.py
El punto de entrada main.py trae el paquete my_app/ entero; los módulos internos usan imports relativos para llegar entre sí. Cada subcarpeta tiene su propio __init__.py que reúne la API pública del subpaquete.

Desde main.py llamas hacia fuera con imports absolutos como from my_app.utility import validate_user. Dentro de utility/, validator.py llega a helper.py con from .helper import log_message — un import relativo. Esa es la división de tareas canónica.

La frontera entre absoluto y relativo
my_app/utility/main.pyvalidator.pyhelper.pyabsolutorelativo
main.py llega al paquete utility con absoluto (ruta desde la raíz); dentro de utility, validator → helper va relativo (ruta desde donde estás). Conocer la frontera evita que renombrar carpetas después se propague.

Implementa validator.py dentro del paquete my_app/utility/ tú mismo y llámalo desde main.py (abre validator.py desde 📂 Files, edita y guarda con Cmd+S o 💾). helper.py ya está hecho.

① Implementa validate_user(email) en validator.py:

- Empieza con from .helper import log_message — un import relativo de helper.py en la misma carpeta

- Si email contiene tanto @ como ., llama log_message("OK: " + email) y devuelve True

- Si no, llama log_message("problema detectado: " + email) y devuelve False

② En main.py, impórtalo con import absoluto (from my_app.utility import validate_user) e imprime print(validate_user("ana@example.com")).

Editor Python

Ejecutar el código para ver el resultado

A partir de aquí es avanzado — siéntete libre de volver atrás si te atascas

El siguiente ejercicio es la prueba final — construir dos paquetes en paralelo. Lo pasarás mejor si el patrón de re-exportar en __init__.py te resulta natural y la distinción entre imports absolutos / relativos la tienes sólida. Si te atascas, vuelve al ejercicio anterior de validator.py o al diagrama «Una disposición al estilo de un proyecto real» de arriba para reorientarte antes de afrontarlo.

Un reto multi-carpeta

Hora de probar construir dos paquetes en paralelo con todo lo aprendido. Vas a manejar un paquete catalog/ de productos y un paquete billing/ en carpetas separadas, y luego orquestarlos desde main.py para completar un pedido. Repartir responsabilidades entre carpetas significa que los cambios en datos de producto solo tocan catalog/, y los cambios en el formato de la factura solo tocan billing/ — el radio de impacto se queda dentro de una sola carpeta.

Disposición del proyecto (catalog y billing como dos paquetes)
project/ ← raíz del proyecto
  • main.py — punto de entrada (usa ambos paquetes para gestionar un pedido)
catalog/
  • __init__.py — re-exporta from .products import get_price
  • products.py — implementa get_price(name)
billing/
  • __init__.py — re-exporta from .invoice import format_invoice
  • invoice.py — implementa format_invoice(name, qty, unit_price)
catalog/ contiene los datos de productos (products.py); billing/ contiene el formato de facturas (invoice.py). Cada __init__.py re-exporta la API pública, así main.py puede llamar hacia fuera con dos imports absolutos: from catalog import get_price y from billing import format_invoice.
main.py orquestando dos paquetes
catalogget_price()main.pyorquesta ambosbillingformat_invoice()from catalog import get_pricefrom billing import ...
main.py llama hacia fuera con imports absolutos para tomar unit_price desde catalog y formatear la factura vía billing. Cada paquete usa internamente imports relativos en __init__.py para re-exportar .products / .invoice.

Completa los paquetes catalog/ y billing/ adjuntos incluyendo sus archivos __init__.py, luego orquéstalos desde main.py con imports absolutos (abre cada archivo desde 📂 Files y guarda con Cmd+S o 💾).

① En catalog/products.py, implementa get_price(name)"apple" → 100, "orange" → 150, en otro caso 0 (un dict + dict.get(name, 0) es cómodo).

② En catalog/__init__.py, escribe from .products import get_price para que desde fuera se pueda llamar from catalog import get_price (un import relativo).

③ En billing/invoice.py, implementa format_invoice(name, qty, unit_price) — calcula qty * unit_price y devuelve una cadena como "apple x 3 = $300".

④ En billing/__init__.py, escribe from .invoice import format_invoice.

⑤ En main.py, haz from catalog import get_price y from billing import format_invoice (imports absolutos). Pide 3 unidades de "apple", busca el precio unitario, formatea la factura e imprime con print().

Editor Python

Ejecutar el código para ver el resultado

A partir de aquí es material extra — raro en código real

La sección de abajo cubre __all__ y es solo informativa. El código del mundo real casi nunca usa from package import *, así que esta parte no afecta al hilo principal. Mientras tengas dominada la forma de import con nombres explícitos, siéntete libre de leer esta sección por encima.

Controlar from package import * con __all__

Cuando alguien escribe from my_package import * para traerlo todo con un asterisco, puedes controlar qué se expone vía __all__ en __init__.py. Con __all__ = ["add"], el import con * solo recoge add — nada más.

__all__ reduce lo que se expone
__init__.pyfrom .calc importadd, multiply__all__ = ["add"]from pkgimport *add→ traídomultiply→ no traído(NameError)
Con __all__ = ["add"] en __init__.py, solo add pasa a través de from package import *. multiply queda fuera (todavía puedes tomarlo explícitamente).
# my_package/__init__.py
from .calculation import add, multiply

__all__ = ["add"]   # solo add se expone vía *

# Lado del llamador
# from my_package import *
# add(1, 2)        # OK
# multiply(1, 2)   # NameError (no traído por *)

En la práctica, evita los imports con *

from my_package import * es difícil de leer — no puedes ver de un vistazo qué entró. El código real casi siempre usa nombres explícitos como from my_package import add, multiply. Trata __all__ como una *red de seguridad para el caso raro en que alguien sí use ``**.

Verifica el comportamiento de __all__ con el paquete bundle/ adjunto. Abre bundle/__init__.py desde 📂 Files — trae tanto add como multiply pero limita la exposición con __all__ = ["add"].

① En main.py, haz from bundle import * e imprime add(2, 3).

② Después envuelve una llamada a multiply(2, 3) en try/except y verifica que lanza NameError (este ejercicio usa el runtime Pyodide, compatible con CPython — la primera carga tarda 5–15 segundos).

Editor Python

Ejecutar el código para ver el resultado

Este artículo cubrió usar __init__.py para agrupar varios módulos en un solo paquete, la diferencia entre from package import name e import package, elegir entre imports absolutos y relativos, y una disposición de carpetas cercana a un proyecto real.

QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué archivo debes colocar en una carpeta para que sea reconocida como un paquete?

Pregunta 2Desde my_app/utility/validator.py, ¿cuál es el import relativo correcto para cargar helper.py en la misma carpeta utility?

Pregunta 3¿Qué pasa si escribes un import relativo (from . import xxx) dentro de un punto de entrada (un archivo que ejecutas directamente con python main.py)?