Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_desde_cero_con_python:modulos_paquetes_e_importaciones

Módulos, paquetes e importaciones

Organizar el código en varios archivos.

Este módulo forma parte del curso de programación desde cero con Python

El programa monolítico que ya no cabe en la cabeza

Imagina que tu analizador de gastos del Módulo 10 crece: añades funciones para leer archivos CSV, para generar gráficos, para enviar correos, para validar datos… El archivo main.py supera las 800 líneas. Encontrar una función concreta se vuelve una caza. Y si quieres reutilizar la función calcular_media en otro proyecto, tienes que copiarla y pegar.

Los módulos resuelven esto: un archivo = una unidad lógica. gastos.py contiene las funciones de cálculo; interfaz.py, las de entrada/salida; main.py, solo el punto de entrada. Cada archivo es independiente, se puede probar por separado y se puede importar desde cualquier otro. La biblioteca estándar de Python funciona exactamente así: son cientos de módulos listos para importar (math, datetime, json, pathlib…). Aprender a organizar y a importar es aprender a reutilizar en lugar de reinventar.

¿Qué es un módulo?

Un módulo es simplemente un archivo .py que contiene definiciones de funciones, clases y variables. Puedes importarlo desde otro archivo para usar su contenido.

# archivo: matematicas.py
def sumar(a, b):
    """Devuelve la suma de a y b."""
    return a + b
 
PI = 3.14159
# archivo: main.py
import matematicas
 
print(matematicas.sumar(2, 3))  # 5
print(matematicas.PI)           # 3.14159

Al importar matematicas, Python busca matematicas.py en el directorio actual (y en las rutas de sys.path), lo ejecuta y expone sus nombres a través del objeto matematicas.

[PYTHON] Un módulo es un objeto de tipo module. Puedes inspeccionar sus atributos con dir(matematicas) y obtener su ruta con matematicas.__file__. Esta introspección es útil para depurar problemas de importación.
[ERROR HABITUAL] Nombrar tu módulo igual que un módulo de la biblioteca estándar (por ejemplo, random.py o json.py). Python encontrará tu archivo primero y la importación del módulo estándar fallará o se comportará de forma inesperada. Evita nombres como math.py, os.py, time.py, datetime.py, test.py. Si necesitas un nombre similar, añade un prefijo: mi_random.py.

La biblioteca estándar

Python viene con una biblioteca estándar de más de 200 módulos que cubren tareas comunes: matemáticas (math), fechas (datetime), archivos (os, pathlib), JSON (json), expresiones regulares (re), pruebas (unittest), etc. No necesitas instalar nada para usarlos.

import math
print(math.sqrt(16))  # 4.0
 
from datetime import date
print(date.today())   # 2026-10-31
[BUENA PRÁCTICA] Antes de escribir una función desde cero, comprueba si la biblioteca estándar ya la ofrece. La documentación oficial está organizada por módulos y es la referencia principal. Ahorrarás tiempo y evitarás errores.

Formas de importar

Hay tres estilos principales:

1. import modulo: importa el módulo completo y accedes a sus nombres con modulo.nombre.

import math
print(math.sqrt(25))

2. from modulo import nombre: importa nombres específicos directamente al espacio actual.

from math import sqrt, pi
print(sqrt(25))  # 5.0
print(pi)        # 3.14159...

3. from modulo import *: importa todos los nombres públicos. No recomendado salvo en sesiones interactivas o scripts muy simples.

from math import *  # Evítalo en código de producción
print(sqrt(25))

Alias con as: puedes renombrar un módulo o un nombre al importarlo, para abreviar o evitar conflictos.

import datetime as dt
print(dt.date.today())
 
from math import sqrt as raiz
print(raiz(25))
[ERROR HABITUAL] from modulo import * contamina el espacio de nombres y puede sobrescribir funciones o variables propias sin aviso. Además, oculta de dónde viene cada nombre, dificultando la lectura. PEP 8 lo desaconseja explícitamente. Usa import modulo o from modulo import nombre_concreto.
[BUENA PRÁCTICA] El orden de las importaciones según PEP 8 es: (1) biblioteca estándar, (2) bibliotecas de terceros, (3) módulos propios. Cada grupo separado por una línea en blanco. Esto facilita ver las dependencias de un vistazo.

5.4. Crear módulos y paquetes

Un paquete es una carpeta que contiene módulos y un archivo especial llamado __init__.py (que puede estar vacío). El __init__.py marca la carpeta como paquete y se ejecuta al importarlo por primera vez.

Estructura típica:

mi_proyecto/
├── main.py
└── utilidades/
    ├── __init__.py
    ├── matematicas.py
    └── texto.py

main.py puede importar los módulos del paquete:

from utilidades import matematicas
from utilidades.texto import limpiar
 
print(matematicas.sumar(2, 3))
print(limpiar("  hola  "))
[PYTHON] Desde Python 3.3, existen los paquetes de espacio de nombres (namespace packages), que no requieren __init__.py. Sin embargo, para la mayoría de proyectos, usar __init__.py explícito es más claro y predecible. La guía oficial de empaquetado recomienda la estructura con __init__.py para paquetes regulares.
[ERROR HABITUAL] Olvidar el __init__.py en un paquete que esperas importar como tal. Aunque Python 3.3+ permite paquetes sin él en ciertos casos, el comportamiento puede variar según la configuración. Si tienes dudas, crea el __init__.py vacío.

if __name__ == "__main__": script y módulo a la vez

Cuando Python ejecuta un archivo directamente (por ejemplo, python main.py), asigna a la variable global __name__ el valor "__main__". Cuando el mismo archivo se importa desde otro, __name__ toma el nombre del módulo (por ejemplo, "main" o "utilidades.matematicas").

Esto permite que un archivo funcione como script ejecutable y como módulo importable:

# archivo: calculadora.py
def sumar(a, b):
    return a + b
 
def main():
    print(sumar(2, 3))
 
if __name__ == "__main__":
    main()

- Si ejecutas python calculadora.py, se ejecuta main() y muestra 5. - Si haces import calculadora desde otro archivo, no se ejecuta main(); solo se definen las funciones.

[PYTHON] Este patrón es el idioma estándar para el punto de entrada de un script en Python. La documentación oficial del módulo __main__ lo describe como la forma de comprobar si el módulo se está ejecutando en el entorno de código de máximo nivel. Es una convención que verás en prácticamente todos los proyectos profesionales.
[ERROR HABITUAL] Poner código ejecutable (impresiones, llamadas a input(), cálculos) fuera del if __name__ == "__main__". Si otro archivo importa tu módulo, ese código se ejecutará sin querer. Todo el código que no sea definiciones debe ir dentro del bloque main o de funciones.

5.6. Importaciones absolutas y relativas

Importación absoluta: especifica la ruta completa desde la raíz del proyecto.

from utilidades.matematicas import sumar

Importación relativa: usa puntos para navegar dentro del paquete actual.

# Dentro de utilidades/texto.py
from .matematicas import sumar   # . = paquete actual
from ..config import CONFIG      # .. = paquete padre
[BUENA PRÁCTICA] PEP 8 y la guía oficial de empaquetado recomiendan importaciones absolutas para la mayoría de los casos, porque son más explícitas y no dependen de la ubicación del archivo actual. Las importaciones relativas son útiles dentro de un paquete grande, pero solo funcionan cuando el módulo se ejecuta como parte del paquete (por ejemplo, con python -m), no cuando se ejecuta directamente el archivo.
[ERROR HABITUAL] Usar importaciones relativas en un script que se ejecuta directamente (python mi_paquete/modulo.py). El script no forma parte del paquete en ese contexto y la importación falla con ImportError: attempted relative import with no known parent package. Ejecuta con python -m mi_paquete.modulo o usa importaciones absolutas.

5.7. `__main__.py`: paquetes ejecutables

Un paquete puede tener un archivo __main__.py. Cuando ejecutas el paquete con python -m nombre_paquete, Python ejecuta ese archivo.

mi_proyecto/
├── __init__.py
├── __main__.py
└── logica.py
# archivo: __main__.py
from logica import ejecutar
 
if __name__ == "__main__":
    ejecutar()

Desde la terminal:

- Windows: py -m mi_proyecto - Linux: python3 -m mi_proyecto

[PYTHON] python -m paquete es la forma recomendada de ejecutar paquetes. A diferencia de python paquete/__main__.py, el flag -m añade el directorio actual a sys.path y ejecuta el módulo como parte del paquete, lo que permite importaciones relativas y un comportamiento más predecible.

5.8. Novedad de Python 3.15: importaciones perezosas (`lazy import`)

Una de las novedades más importantes de Python 3.15 (PEP 810) es la posibilidad de retrasar la carga de un módulo hasta que se use por primera vez. Esto reduce el tiempo de arranque en aplicaciones grandes.

lazy import json
lazy from pathlib import Path
 
print("Iniciando...")  # json y pathlib aún no se han cargado
datos = json.loads('{"clave": "valor"}')  # aquí se carga json
ruta = Path(".")  # aquí se carga pathlib

La palabra clave lazy es un soft keyword: solo tiene significado en el contexto de una importación. Funciona con import y from ... import. Si el módulo no existe, el error se lanza en el momento del primer uso, no en la importación.

[AMPLIACIÓN] Las importaciones perezosas solo se permiten a nivel de módulo (no dentro de funciones, clases ni bloques try). Tampoco se pueden usar con from modulo import * ni con from __future__. Para código que debe ser compatible con versiones anteriores a 3.15, se puede usar la variable __lazy_modules__ con una lista de nombres de módulos. En Python 3.15 también existen las opciones -X lazy_imports y la variable de entorno PYTHON_LAZY_IMPORTS para activar el modo perezoso globalmente.
[BUENA PRÁCTICA] No marques como lazy todos los imports por defecto. Úsalo solo para módulos pesados que no se usan en todos los caminos de ejecución (por ejemplo, un módulo de generación de gráficos en una aplicación de consola). Los imports ligeros y siempre necesarios es mejor cargarlos de forma normal.

5.9. Conflictos de nombres e importaciones circulares

Conflicto de nombres: si tienes un archivo random.py en tu proyecto y haces import random, Python importará tu archivo, no el módulo estándar. Solución: renombra tu archivo o usa un paquete para aislarlo.

Importación circular: el módulo A importa B y B importa A.

# a.py
import b
 
def funcion_a():
    b.funcion_b()
 
# b.py
import a
 
def funcion_b():
    a.funcion_a()

Al importar a, Python empieza a ejecutarlo, encuentra import b, empieza a ejecutar b, encuentra import a (que está a medio cargar) y puede fallar con ImportError o comportarse de forma impredecible.

[ERROR HABITUAL] Las importaciones circulares suelen indicar un problema de diseño: dos módulos que dependen demasiado el uno del otro. La solución habitual es extraer la lógica compartida a un tercer módulo (comun.py) que ambos importen, o mover la importación dentro de la función (importación diferida) si es inevitable. Las importaciones perezosas de Python 3.15 también pueden ayudar en algunos casos, pero no sustituyen a un buen diseño.
[PYTHON] En Python 3.15 se ha corregido una regresión relacionada con importaciones circulares entre hilos (threads), asegurando que importlib.import_module devuelva el módulo parcialmente inicializado en un escenario de importación circular entre hilos, igual que hace la sentencia import. Es un detalle avanzado, pero relevante si trabajas con concurrencia.

6. Ejemplos comentados

Ejemplo 1: Módulo propio con if __name__ == "__main__".

# archivo: operaciones.py
def sumar(a, b):
    """Devuelve la suma de a y b."""
    return a + b
 
def restar(a, b):
    """Devuelve la resta de a y b."""
    return a - b
 
if __name__ == "__main__":
    print("Prueba rápida:", sumar(5, 3))  # Solo se ejecuta como script
# archivo: main.py
from operaciones import sumar
 
print(sumar(10, 20))  # 30. No se ejecuta la prueba rápida.

Seguimiento: Al importar operaciones, __name__ es "operaciones", no "__main__", así que el bloque de prueba no se ejecuta. Al ejecutar operaciones.py directamente, sí.

Ejemplo 2: Paquete con __main__.py.

calculadora/
├── __init__.py
├── operaciones.py
└── __main__.py
# archivo: calculadora/__main__.py
from .operaciones import sumar
 
def main():
    print("Resultado:", sumar(2, 3))
 
if __name__ == "__main__":
    main()

Desde la terminal: python -m calculadora (Linux) o py -m calculadora (Windows). La importación relativa .operaciones funciona porque el paquete se ejecuta con -m.

Ejemplo 3: Importación perezosa (Python 3.15).

lazy import json
 
def procesar(datos_texto):
    # json no se ha cargado hasta que se llama a esta función
    return json.loads(datos_texto)
 
print("Programa iniciado.")  # json aún no cargado
resultado = procesar('{"x": 1}')  # aquí se carga json
print(resultado)

Seguimiento: El programa arranca más rápido porque json no se carga hasta que procesar se llama por primera vez. Si json no existiera, el error aparecería en procesar, no en el import.


7. Seguimiento de la ejecución

Cuando Python ejecuta import modulo, sigue estos pasos:

1. Busca modulo.py en el directorio actual y en las rutas de sys.path. 2. Si el módulo no está ya cargado en sys.modules, lo carga: lee el archivo, lo compila a bytecode y ejecuta todo su código de nivel superior (definiciones de funciones, asignaciones, etc.). 3. Si el módulo ya está en sys.modules, no lo vuelve a ejecutar; reutiliza el objeto existente. 4. Asocia el nombre modulo al objeto módulo en el espacio de nombres actual.

Este comportamiento explica dos cosas: (1) el código de nivel superior de un módulo se ejecuta una sola vez, aunque se importe desde varios sitios; (2) las importaciones circulares pueden encontrar módulos parcialmente inicializados, porque el módulo A se está ejecutando cuando B intenta importarlo.


8. Errores habituales

ErrorCausaCómo diagnosticarlo
ModuleNotFoundErrorEl módulo no está en sys.path o el nombre está mal escrito.Comprueba la ubicación del archivo y el nombre. Usa import sys; print(sys.path).
ImportError: attempted relative import with no known parent packageImportación relativa en un script ejecutado directamente.Ejecuta con python -m paquete.modulo o usa imports absolutos.
El módulo estándar no se importa (se importa tu archivo)Conflicto de nombres (random.py, json.py).Renombra tu archivo.
Código de prueba se ejecuta al importarCódigo ejecutable fuera de if __name__ == "__main__".Mueve el código al bloque main.
ImportError por importación circularA importa B y B importa A.Extrae la lógica compartida a un tercer módulo.
AttributeError: module has no attributeEl nombre no existe en el módulo o la importación falló silenciosamente.Revisa el nombre y la ortografía. Usa dir(modulo).
[ERROR HABITUAL] ModuleNotFoundError al ejecutar desde una carpeta distinta a la del proyecto. La terminal busca el módulo en el directorio actual; si no está ahí, no lo encuentra. Asegúrate de ejecutar desde la raíz del proyecto o ajusta sys.path (aunque esto último no es recomendable en código de producción).

9. Particularidades de Python frente a otros lenguajes

- Los módulos son objetos: en Python, un módulo es un objeto de primera clase. Puedes pasarlo como argumento, asignarlo a una variable e inspeccionarlo. En C, los módulos son archivos de cabecera; en Java, paquetes de clases. - El código de nivel superior se ejecuta al importar: en C o Java, la importación es una operación de compilación/linkado. En Python, importar un módulo ejecuta su código. Esto es potente pero exige disciplina (de ahí el if __name__ == "__main__"). - Importaciones perezosas (lazy import): Python 3.15 introduce una sintaxis específica para retrasar la carga, algo que en otros lenguajes requiere mecanismos manuales o de carga dinámica. - __init__.py: en Python, una carpeta se convierte en paquete añadiendo este archivo. En Java, un paquete es una convención de directorios sin archivo marcador.


10. Buenas prácticas

Incorrecto (todo en un archivo, código de prueba fuera del main):

# main.py
def sumar(a, b): ...
def restar(a, b): ...
print(sumar(2, 3))  # Se ejecuta al importar

Mejor (módulos separados, punto de entrada claro):

# operaciones.py
def sumar(a, b): ...
def restar(a, b): ...
 
# main.py
from operaciones import sumar
 
def main():
    print(sumar(2, 3))
 
if __name__ == "__main__":
    main()
[BUENA PRÁCTICA] Sigue la estructura de proyecto recomendada por la guía oficial de empaquetado: un archivo pyproject.toml en la raíz, un paquete con __init__.py, y un módulo __main__.py o un script de entrada. Mantén las importaciones en el orden de PEP 8 (estándar, terceros, propios). Documenta cada módulo con una docstring al principio.

11. Windows y Linux

- Ejecutar módulos: en Windows, py -m paquete.modulo; en Linux, python3 -m paquete.modulo. El flag -m funciona igual en ambos sistemas. - Rutas en sys.path: Windows usa \ y letras de unidad (C:\); Linux usa /. Python normaliza las rutas internamente, pero si construyes rutas manualmente, usa pathlib (Módulo 13) para que sean portables. - __pycache__: Python genera archivos .pyc en una carpeta __pycache__ al importar un módulo. Funciona igual en ambos sistemas y no necesitas gestionarla manualmente (añádela a .gitignore en el Módulo 19). - __package__ y __cached__: en Python 3.15, estos atributos de módulo dejan de establecerse o tenerse en cuenta por el sistema de importación. Si tu código dependía de ellos, debes actualizarlo. Es una novedad relevante para código avanzado.

[WINDOWS / LINUX] Si un script funciona en Linux pero falla en Windows con ModuleNotFoundError, revisa las mayúsculas y minúsculas en los nombres de archivo. Windows no distingue mayúsculas en los nombres de archivo; Linux sí. import Utilidades falla en Linux si la carpeta se llama utilidades.

12. Ejercicios guiados

Ejercicio guiado 1: Crear un módulo.

1. Crea un archivo saludos.py con una función saludar(nombre) que devuelva f"Hola, {nombre}". 2. Crea un archivo main.py que importe saludos y llame a saludar("Ana"). 3. Ejecuta main.py y comprueba el resultado. 4. Añade a saludos.py un bloque if __name__ == "__main__" que imprima un saludo de prueba. Comprueba que no se ejecuta al importar desde main.py.

Ejercicio guiado 2: Paquete ejecutable.

1. Crea una carpeta mi_paquete con __init__.py, operaciones.py (con sumar y restar) y __main__.py. 2. En __main__.py, importa sumar con una importación relativa y llama a main(). 3. Ejecuta el paquete con python -m mi_paquete (Linux) o py -m mi_paquete (Windows). 4. ¿Qué ocurre si ejecutas python mi_paquete/__main__.py directamente? Explica el error.

Ejercicio guiado 3: Importación perezosa.

Escribe un script que use lazy import json y lazy import csv. Imprime un mensaje antes de usar json, luego usa json.loads con una cadena simple, y después imprime otro mensaje. Observa que el programa arranca sin cargar los módulos hasta que se usan.


13. Ejercicios autónomos

1. Predicción: ¿Qué muestra este código? Razona antes de ejecutar.

  ```python
  # modulo.py
  print("Cargando modulo...")
  def f():
      return 1
  # main.py
  import modulo
  print("Main")
  ```
  ¿Qué ocurre si `main.py` importa `modulo` dos veces?

2. Conflicto de nombres: Crea un archivo random.py que imprima "Mi módulo". Luego, en otro archivo, haz import random. ¿Qué crees que ocurre? ¿Cómo se soluciona?

3. Importación circular: Crea a.py y b.py que se importen mutuamente y llamen a una función del otro. Ejecuta python a.py y observa el error. Propón una solución extrayendo la lógica a comun.py.

4. Paquete completo: Crea un paquete utilidades con matematicas.py (suma, resta), texto.py (mayúsculas, minúsculas) y __main__.py que pruebe ambas. Ejecútalo con -m.

5. Reto opcional: Investiga la variable __lazy_modules__ de Python 3.15 y escribe un módulo que la use para marcar json como perezoso sin usar la palabra clave lazy, de modo que el código sea compatible con versiones anteriores a 3.15.


14. Soluciones razonadas

Solución al ejercicio guiado 1:

# saludos.py
def saludar(nombre):
    return f"Hola, {nombre}"
 
if __name__ == "__main__":
    print("Prueba:", saludar("Test"))
# main.py
import saludos
print(saludos.saludar("Ana"))

Al importar saludos, no se ejecuta la prueba porque __name__ es "saludos".

Solución al ejercicio guiado 2:

# mi_paquete/__main__.py
from .operaciones import sumar
 
def main():
    print("2 + 3 =", sumar(2, 3))
 
if __name__ == "__main__":
    main()

python -m mi_paquete funciona. python mi_paquete/__main__.py falla con ImportError: attempted relative import with no known parent package porque el script no se ejecuta como parte del paquete.

Solución al ejercicio 1 (predicción): main.py muestra "Cargando modulo..." y luego "Main". Al importar modulo por segunda vez, Python no lo vuelve a ejecutar porque ya está en sys.modules.

Solución al ejercicio 2 (conflicto de nombres): import random importa tu archivo random.py, no el módulo estándar. Si luego intentas usar random.randint, falla con AttributeError. Solución: renombrar tu archivo a mi_random.py.

Solución al reto opcional (orientativa):

# modulo_lazy.py
__lazy_modules__ = ["json"]
 
import json
 
def procesar(datos):
    return json.loads(datos)

En Python 3.15+, json se carga de forma perezosa. En versiones anteriores, la variable __lazy_modules__ se ignora y el import es normal, manteniendo la compatibilidad.


15. Resumen

- Un módulo es un archivo .py; un paquete es una carpeta con __init__.py y módulos. - La biblioteca estándar ofrece módulos listos para importar (math, datetime, json, pathlib…). - import modulo, from modulo import nombre y import modulo as alias son las formas básicas de importar. - if __name__ == "__main__" permite que un archivo sea script y módulo importable a la vez. - Las importaciones absolutas son preferibles; las relativas solo funcionan dentro de un paquete ejecutado con -m. - Un paquete ejecutable usa __main__.py y se lanza con python -m paquete. - Python 3.15 introduce importaciones perezosas (lazy import): el módulo se carga en el primer uso, no al importar. - Evita conflictos de nombres (no llames a tus archivos random.py, json.py…) y diseña para evitar importaciones circulares. - En Python 3.15, __package__ y __cached__ dejan de ser establecidos por el sistema de importación.


16. Autoevaluación

1. ¿Qué diferencia hay entre un módulo y un paquete? ¿Qué papel juega __init__.py? 2. ¿Para qué sirve if __name__ == "__main__"? Pon un ejemplo en el que su ausencia cause un problema. 3. ¿Cuándo usarías una importación relativa y cuándo una absoluta? 4. ¿Qué hace lazy import y en qué situaciones es útil? 5. Reto: Sin ejecutar, predice qué ocurre si tienes un archivo math.py en tu proyecto y ejecutas import math. ¿Qué módulo se importa? ¿Cómo lo solucionas?


17. Fuentes oficiales

- Tutorial oficial: Modules: https://docs.python.org/3.15/tutorial/modules.html - Referencia del lenguaje: The import system: https://docs.python.org/3.15/reference/import.html - Biblioteca estándar: __main__ — Top-level code environment: https://docs.python.org/3.15/library/__main__.html - Novedades de Python 3.15 (importaciones perezosas, PEP 810): https://docs.python.org/3/whatsnew/3.15.html - PEP 810 – Explicit lazy imports: https://peps.python.org/pep-0810/ - Deprecaciones pendientes en Python 3.15 (__package__, __cached__): https://docs.python.org/3.13/deprecations/pending-removal-in-3.15.html - Guía oficial de empaquetado: pyproject.toml: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ - PEP 8 – Importaciones: https://peps.python.org/pep-0008/#imports

informatica/programacion/cursos/programacion_desde_cero_con_python/modulos_paquetes_e_importaciones.txt · Última modificación: por tempwin