El módulo importlib expone la maquinaria interna del sistema de imports de Python. Con él puedes importar módulos especificando su nombre como cadena de texto, cargar ficheros .py desde rutas arbitrarias fuera del sys.path, recargar módulos en caliente durante el desarrollo y construir sistemas de plugins que se descubren automáticamente sin tocar el código principal.
importlib.import_module(): importar por nombre
import importlib
# Equivalente a: import json
json = importlib.import_module('json')
print(json.dumps({"clave": "valor"}))
# Equivalente a: from pathlib import Path
Path = importlib.import_module('pathlib').Path
print(Path('/tmp').exists())
# Importar submódulos
logging_handlers = importlib.import_module('logging.handlers')
print(dir(logging_handlers))
Importación dinámica según configuración
import importlib
BACKENDS_DISPONIBLES = {
'json': 'json',
'yaml': 'yaml',
'toml': 'tomllib',
}
def cargar_backend(nombre: str):
modulo_nombre = BACKENDS_DISPONIBLES.get(nombre)
if not modulo_nombre:
raise ValueError(f"Backend desconocido: {nombre}")
try:
return importlib.import_module(modulo_nombre)
except ImportError as e:
raise ImportError(f"No se pudo cargar el backend '{nombre}': {e}") from e
# Cargar según configuración en tiempo de ejecución
backend_nombre = 'json' # podría venir de una variable de entorno
backend = cargar_backend(backend_nombre)
print(backend.dumps({"config": True}))
importlib.util: cargar ficheros desde rutas arbitrarias
import importlib.util
import sys
from pathlib import Path
def cargar_modulo_desde_ruta(ruta: str | Path, nombre: str = None):
"""Carga un fichero .py desde cualquier ruta del sistema."""
ruta = Path(ruta)
if nombre is None:
nombre = ruta.stem # nombre del fichero sin extensión
spec = importlib.util.spec_from_file_location(nombre, ruta)
if spec is None or spec.loader is None:
raise ImportError(f"No se pudo crear spec para {ruta}")
modulo = importlib.util.module_from_spec(spec)
sys.modules[nombre] = modulo # registrar para que otros imports lo encuentren
spec.loader.exec_module(modulo)
return modulo
# Cargar un plugin desde /opt/plugins/mi_plugin.py
# plugin = cargar_modulo_desde_ruta('/opt/plugins/mi_plugin.py')
# plugin.inicializar()
importlib.reload(): recargar módulos en caliente
import importlib
import time
import mi_modulo_configurable
def recargar_config():
"""Recarga el módulo de configuración sin reiniciar la aplicación."""
importlib.reload(mi_modulo_configurable)
print(f"Configuración recargada: {mi_modulo_configurable.CONFIG}")
# En un servidor de larga duración puedes recargar la configuración
# cuando detectas que el fichero ha cambiado:
from pathlib import Path
config_path = Path('mi_modulo_configurable.py')
ultima_modificacion = config_path.stat().st_mtime
while True:
mtime = config_path.stat().st_mtime
if mtime != ultima_modificacion:
recargar_config()
ultima_modificacion = mtime
time.sleep(1)
Sistema de plugins: escanear un directorio
import importlib
import importlib.util
import sys
from pathlib import Path
class RegistroPlugins:
def __init__(self):
self._plugins: dict[str, object] = {}
def descubrir(self, directorio: str | Path):
"""Carga todos los ficheros .py del directorio como plugins."""
directorio = Path(directorio)
for fichero in directorio.glob('*.py'):
if fichero.name.startswith('_'):
continue # ignorar __init__.py, etc.
try:
modulo = self._cargar(fichero)
if hasattr(modulo, 'Plugin'):
instancia = modulo.Plugin()
nombre = getattr(instancia, 'nombre', fichero.stem)
self._plugins[nombre] = instancia
print(f"Plugin cargado: {nombre}")
except Exception as e:
print(f"Error al cargar {fichero}: {e}")
def _cargar(self, ruta: Path):
spec = importlib.util.spec_from_file_location(ruta.stem, ruta)
modulo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(modulo)
return modulo
def obtener(self, nombre: str):
return self._plugins.get(nombre)
def listar(self) -> list[str]:
return list(self._plugins.keys())
# Uso:
# registro = RegistroPlugins()
# registro.descubrir('/opt/mi_app/plugins/')
# print(registro.listar())
# plugin = registro.obtener('exportador_csv')
# plugin.exportar(datos)
Entry points: plugins de paquetes instalados
La forma más robusta de hacer plugins instalables es mediante entry points definidos en pyproject.toml. Cuando alguien instala tu plugin con pip, se registra automáticamente.
# pyproject.toml del paquete plugin:
# [project.entry-points."miapp.plugins"]
# mi_plugin = "mi_paquete.plugin:MiPlugin"
# En la aplicación principal, descubrir todos los plugins instalados:
from importlib.metadata import entry_points
def cargar_plugins_instalados(grupo: str) -> dict:
plugins = {}
eps = entry_points(group=grupo)
for ep in eps:
try:
clase_plugin = ep.load()
plugins[ep.name] = clase_plugin()
print(f"Plugin instalado: {ep.name} ({ep.value})")
except Exception as e:
print(f"Error cargando plugin {ep.name}: {e}")
return plugins
# plugins = cargar_plugins_instalados('miapp.plugins')
Comprobar si un módulo está disponible sin importarlo
import importlib.util
def modulo_disponible(nombre: str) -> bool:
return importlib.util.find_spec(nombre) is not None
if modulo_disponible('ujson'):
import ujson as json # más rápido
else:
import json
if modulo_disponible('numpy'):
print("NumPy disponible")
else:
print("NumPy no instalado")
__import__ vs importlib.import_module
La función builtin __import__ existe pero tiene un comportamiento poco intuitivo con paquetes y submódulos. importlib.import_module() es la alternativa moderna y clara para importación dinámica: úsala siempre en lugar de __import__ salvo que estés escribiendo un hook de import personalizado.
