Pregunta 1¿Qué archivo debes colocar en una carpeta para que sea reconocida como un paquete?
__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.
- __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.)
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.
from my_package import add sin pensar en qué archivo (calculation) lo contiene.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 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))
| Forma | Nombre en el ámbito | Cómo llamarla |
|---|---|---|
| from my_package.calculation import add | add | add(1, 2) |
| import my_package.calculation | my_package | my_package.calculation.add(1, 2) |
| import my_package.calculation as calc | calc | calc.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.
- 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
- 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.adddeja 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).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.
- main.py ← el punto de entrada que ejecutas primero
- config.py ← ajustes de toda la app
- __init__.py ← se ejecuta cuando ocurre
import my_app
- __init__.py
- validator.py
- helper.py
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.
| Tipo | Forma | Qué significa |
|---|---|---|
| Import absoluto | from my_app.utility import helper | Ruta completa desde la raíz del proyecto |
| Import relativo | from . import helper | Relativo al archivo en el que estás |
| Import relativo (padre) | from .. import config | Apunta a un archivo una carpeta arriba |
. 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
- config.py ← objetivo de
from ..config import ...
- validator.py ← escribe
from .helper import log_message - helper.py ← provee
log_message(...)
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).
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:
- main.py — punto de entrada (el archivo que ejecutas con python)
- config.py — ajustes de toda la app
- __init__.py — API pública del paquete my_app
- __init__.py
- connection.py / models.py
- __init__.py
- validator.py / helper.py
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.
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.
- main.py — punto de entrada (usa ambos paquetes para gestionar un pedido)
- __init__.py — re-exporta
from .products import get_price - products.py — implementa
get_price(name)
- __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.__init__.py para re-exportar .products / .invoice.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__ = ["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 ``**.
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.
Verificación de conocimientos
Responde cada pregunta una a una.
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)?