W3docs

Paquetes de Python y el Sistema de Importación

Aprende cómo funcionan los paquetes de Python: crea un paquete con __init__.py, usa importaciones absolutas y relativas, expón una API pública limpia y evita errores comunes.

Un paquete es un directorio de módulos de Python que se trata como una única unidad importable. Donde un módulo es un solo archivo .py, un paquete es una carpeta — que puede contener muchos módulos y sub-paquetes — que el sistema de importación de Python puede recorrer como un árbol. Este capítulo explica cómo crear paquetes, controlar lo que exponen, escribir importaciones absolutas y relativas correctamente, y evitar los problemas que confunden a los principiantes.

Módulos vs. Paquetes — la Diferencia Clave

Un módulo es un solo archivo .py:

greetings.py        ← module

Un paquete es un directorio que contiene al menos un archivo especial llamado __init__.py:

greetings/          ← package
    __init__.py
    english.py
    spanish.py

Ambos se importan con la misma palabra clave import, pero un paquete te ofrece una jerarquía de espacios de nombres: greetings.english y greetings.spanish son módulos separados, pero comparten el espacio de nombres greetings.

Cuándo usar un módulo vs. un paquete:

SituaciónUsar
Una utilidad pequeña y autocontenidaMódulo (un archivo .py)
Varios módulos relacionados que quieres bajo un mismo nombrePaquete (un directorio)
Una biblioteca que pretendes distribuir en PyPIPaquete (con estructura src/)

El Archivo __init__.py

__init__.py es lo que convierte un directorio en un paquete. Python lo ejecuta la primera vez que se importa el paquete (o cualquiera de sus módulos). Puede estar vacío, o puede:

  • Importar nombres de sub-módulos para que estén disponibles a nivel del paquete
  • Ejecutar inicialización a nivel del paquete (configuración de logging, comprobaciones de versión, etc.)
  • Definir __all__ para controlar from package import *

Estructura mínima de un paquete

myapp/
    __init__.py       ← can be empty
    utils.py
    config.py
# myapp/__init__.py  (empty — that is fine)
# myapp/utils.py
def greet(name):
    return f"Hello, {name}!"

Importar desde fuera del paquete:

from myapp.utils import greet

print(greet("Alice"))   # Hello, Alice!

Exponer nombres a nivel del paquete

Un patrón habitual es importar los nombres más utilizados en __init__.py para que los usuarios puedan escribir from myapp import greet en lugar de from myapp.utils import greet.

# myapp/__init__.py
from .utils import greet
from .config import MAX_RETRIES

Ahora ambos nombres están disponibles directamente en el paquete:

import myapp

print(myapp.greet("Bob"))   # Hello, Bob!
print(myapp.MAX_RETRIES)    # whatever config.py defines

Importaciones Absolutas

Una importación absoluta siempre parte del paquete de nivel superior o de un directorio en sys.path. Nunca depende de dónde se encuentre el archivo que realiza la importación.

project/
    myapp/
        __init__.py
        utils.py
        services/
            __init__.py
            email.py

Dentro de email.py, una importación absoluta tiene este aspecto:

# myapp/services/email.py
from myapp.utils import greet   # absolute — starts from the top-level package

def send_welcome(user):
    message = greet(user)
    print(f"Sending: {message}")

Las importaciones absolutas son el estilo por defecto y el recomendado (PEP 8). Son inequívocas independientemente de cómo se invoque Python.

Importaciones Relativas

Una importación relativa usa puntos (.) para navegar por el árbol de paquetes relativo a la ubicación del archivo actual.

  • . significa el paquete actual
  • .. significa el paquete padre
  • ... significa el paquete abuelo, y así sucesivamente
# myapp/services/email.py

# One dot — import from myapp.services (same directory)
from . import sms

# Two dots — import from myapp (parent directory)
from ..utils import greet

Cuándo usar importaciones relativas

Las importaciones relativas son útiles dentro de un paquete cuando quieres dejar claro que greet proviene de este paquete y no de alguna biblioteca externa con el mismo nombre. También facilitan la refactorización porque las importaciones se mueven con el paquete si renombras el directorio de nivel superior.

Advertencia: las importaciones relativas solo funcionan dentro de un paquete. Si ejecutas python myapp/utils.py directamente, Python lo trata como un script independiente, no como parte de un paquete, y una importación relativa lanza ImportError: attempted relative import with no known parent package. Ejecuta el paquete con python -m myapp.utils en su lugar.

# Wrong — runs utils.py as a script, breaking relative imports
$ python myapp/utils.py

# Right — runs utils.py as part of the myapp package
$ python -m myapp.utils

Controlar la API Pública con __all__

__all__ es una lista de nombres que from package import * exporta. También documenta qué considera el paquete como público.

# myapp/__init__.py
from .utils import greet, farewell
from .config import MAX_RETRIES

__all__ = ["greet", "MAX_RETRIES"]   # farewell is intentionally not exported

Ahora from myapp import * trae solo greet y MAX_RETRIES. La función farewell sigue existiendo; simplemente no forma parte de la interfaz pública anunciada. Los nombres con un guion bajo inicial (_private) también se excluyen de import * por convención.

Paquetes Anidados (Sub-paquetes)

Los paquetes pueden contener otros paquetes. Cada subdirectorio necesita su propio __init__.py.

analytics/
    __init__.py
    reports/
        __init__.py
        daily.py
        weekly.py
    charts/
        __init__.py
        bar.py
        pie.py

Importa un módulo profundamente anidado con la ruta completa con puntos:

from analytics.reports.daily import generate_report
from analytics.charts.bar import BarChart

O bien, si analytics/__init__.py los expone:

# analytics/__init__.py
from .reports.daily import generate_report
# caller
from analytics import generate_report

¿Hasta qué profundidad deberías llegar?

Un paquete con tres o cuatro niveles de profundidad suele ser una señal de que ha crecido demasiado y debería dividirse en paquetes de nivel superior separados (instalables de forma independiente). Para la mayoría de los proyectos, dos niveles (package.module) es suficiente.

Un Ejemplo Práctico: Construir un Paquete geometry

Construyamos un paquete pequeño pero realista paso a paso.

Estructura de directorios

geometry/
    __init__.py
    shapes.py
    conversions.py

shapes.py

# geometry/shapes.py
import math

def circle_area(radius):
    """Return the area of a circle with the given radius."""
    if radius < 0:
        raise ValueError("radius must be non-negative")
    return math.pi * radius ** 2

def rectangle_area(width, height):
    """Return the area of a rectangle."""
    return width * height

def triangle_area(base, height):
    """Return the area of a triangle."""
    return 0.5 * base * height

conversions.py

# geometry/conversions.py

def degrees_to_radians(degrees):
    """Convert degrees to radians."""
    import math
    return degrees * math.pi / 180

def radians_to_degrees(radians):
    """Convert radians to degrees."""
    import math
    return radians * 180 / math.pi

__init__.py — exponer los nombres clave

# geometry/__init__.py
"""
geometry — simple 2-D geometry utilities.

Public API:
    circle_area(radius) -> float
    rectangle_area(width, height) -> float
    triangle_area(base, height) -> float
    degrees_to_radians(degrees) -> float
    radians_to_degrees(radians) -> float
"""

from .shapes import circle_area, rectangle_area, triangle_area
from .conversions import degrees_to_radians, radians_to_degrees

__all__ = [
    "circle_area",
    "rectangle_area",
    "triangle_area",
    "degrees_to_radians",
    "radians_to_degrees",
]

Usar el paquete

# main.py (sits next to the geometry/ directory)
import geometry

print(geometry.circle_area(5))           # 78.53981633974483
print(geometry.rectangle_area(4, 6))     # 24
print(geometry.degrees_to_radians(90))   # 1.5707963267948966

O con importaciones selectivas:

from geometry import circle_area, degrees_to_radians

print(circle_area(3))              # 28.274333882308138
print(degrees_to_radians(180))     # 3.141592653589793

Paquetes de Espacio de Nombres (Python 3.3+)

Desde Python 3.3, un directorio sin __init__.py es un paquete de espacio de nombres. Python fusiona todos los directorios con el mismo nombre en sys.path en un único paquete lógico. Esto es principalmente útil para grandes organizaciones que dividen un único paquete entre múltiples repositorios o directorios de instalación.

Para el desarrollo cotidiano, incluye siempre __init__.py. Hace que tu intención sea inequívoca y funciona en todas las versiones de Python.

Cómo Encuentra Python los Paquetes

Cuando escribes import geometry, Python busca en sys.path en orden:

  1. El directorio del script en ejecución (o el directorio actual en modo interactivo)
  2. Los directorios en la variable de entorno PYTHONPATH
  3. Los directorios de la biblioteca estándar
  4. El directorio site-packages (donde viven los paquetes instalados con pip)
import sys
print(sys.path)

El directorio del paquete debe estar directamente dentro de una de estas ubicaciones. Si geometry/ está en /home/alice/projects/, Python no lo encontrará a menos que /home/alice/projects/ esté en sys.path.

Consejo: usa un entorno virtual e instala tu paquete en modo de desarrollo (pip install -e .) para que Python siempre lo encuentre sin manipulación manual de sys.path.

Distribuir un Paquete

Para compartir un paquete con otros (o instalarlo con pip), necesitas un pyproject.toml en la raíz del proyecto:

my_project/
    pyproject.toml    ← build metadata
    src/
        geometry/
            __init__.py
            shapes.py
            conversions.py

Un pyproject.toml mínimo:

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.backends.legacy:build"

[project]
name = "geometry"
version = "0.1.0"
description = "Simple 2-D geometry utilities"
requires-python = ">=3.9"

Instálalo localmente en modo editable durante el desarrollo:

pip install -e .

Ahora import geometry funciona en cualquier lugar de tu entorno virtual, independientemente de tu directorio actual.

Errores Comunes

Falta el archivo __init__.py

Si olvidas __init__.py, Python 3 trata el directorio como un paquete de espacio de nombres (que generalmente sigue funcionando), pero Python 2 lo ignora por completo. Sé explícito: añade siempre __init__.py.

Nombrar un paquete igual que un módulo de la biblioteca estándar

Evita nombres como math/, json/, os/, email/. Python puede importar tu paquete en lugar del de la biblioteca estándar, rompiendo código no relacionado.

Ejecutar un módulo de paquete como script

Como se indicó anteriormente, ejecutar python myapp/services/email.py directamente rompe las importaciones relativas. Usa python -m myapp.services.email en su lugar.

Importaciones circulares entre módulos del mismo paquete

Si shapes.py importa de conversions.py y conversions.py importa de shapes.py, tienes una importación circular. Los síntomas incluyen ImportError o nombres que aparecen inesperadamente como None. La solución suele ser mover la lógica compartida a un tercer módulo, o retrasar la importación al interior del cuerpo de una función.

# Delayed import — breaks the cycle at module load time
def some_function():
    from .shapes import circle_area   # imported only when the function is called
    ...

ImportError al usar importaciones relativas fuera de un paquete

# Will raise: ImportError: attempted relative import with no known parent package
# if run as:  python myapp/utils.py

from . import config   # relative import inside utils.py

Ejecútalo como python -m myapp.utils o reestructura para que el punto de entrada sea un script separado que importe el paquete.

Resumen

ConceptoDescripción breve
PaqueteUn directorio con __init__.py que contiene módulos
__init__.pyConvierte un directorio en paquete; se ejecuta en la primera importación
Importación absolutafrom myapp.utils import greet — siempre desde la raíz
Importación relativafrom ..utils import greet — relativa al archivo actual
__all__Lista los nombres exportados por from package import *
Paquete de espacio de nombresDirectorio sin __init__.py; solo Python 3.3+
Instalación editablepip install -e . — paquete encontrado en cualquier lugar del venv

Consulta Módulos de Python para la organización de código en un solo archivo, Python pip para instalar paquetes de terceros, y Entornos Virtuales de Python para mantener aisladas las dependencias del proyecto.

Práctica

Práctica
What makes a directory a Python package (in Python versions before 3.3)?
What makes a directory a Python package (in Python versions before 3.3)?
Was this page helpful?