El módulo pkgutil reúne utilidades para trabajar con el sistema de importación de Python. Permite listar módulos disponibles en determinados paths, recorrer subpaquetes, inspeccionar importers, leer recursos mediante loaders y soportar patrones legacy de namespace packages. Es común en descubrimiento de plugins, herramientas de inspección, registries, CLIs extensibles y diagnóstico de imports.
La importación de Python es dinámica, así que la exploración exige cuidado. Recorrer paquetes puede ejecutar código de inicialización, los loaders personalizados pueden tener comportamiento propio y un nombre encontrado no implica que sea seguro o compatible. Prefiere APIs modernas de importlib cuando cubran el caso, pero comprende pkgutil porque muchas bibliotecas todavía lo utilizan.
Lista módulos con iter_modules
pkgutil.iter_modules() produce información sobre módulos visibles.
import pkgutil
for info in pkgutil.iter_modules():
print(info.name, info.ispkg)
Sin path explícito, considera módulos de nivel superior del entorno actual.
ModuleInfo
Cada resultado contiene finder, nombre e indicador de paquete.
for info in pkgutil.iter_modules():
print(info.module_finder)
print(info.name)
print(info.ispkg)
El finder puede representar un directorio, ZIP o implementación personalizada.
Descubre hijos de un paquete
Pasa el __path__ y un prefijo.
import mi_paquete
import pkgutil
for info in pkgutil.iter_modules(
mi_paquete.__path__,
mi_paquete.__name__ + ".",
):
print(info.name)
El prefijo genera nombres importables completos.
Paquetes frente a módulos
ispkg distingue paquetes con posibles hijos de módulos simples.
No lo infieras solo por extensión. Módulos frozen y loaders especiales pueden no corresponder a un archivo normal.
walk_packages
walk_packages() recorre recursivamente paquetes encontrados.
for info in pkgutil.walk_packages(
mi_paquete.__path__,
mi_paquete.__name__ + ".",
):
print(info.name)
Es más potente y más invasivo que iter_modules().
walk_packages puede importar
Para obtener __path__ de subpaquetes, puede importarlos. Sus __init__.py pueden registrar componentes, leer configuración, abrir conexiones o producir side effects.
No recorras árboles de terceros dentro de un proceso privilegiado sin aislamiento.
Callback onerror
walk_packages() acepta una función para fallos de importación.
def al_error(nombre):
errores.append(nombre)
for info in pkgutil.walk_packages(
paquete.__path__,
paquete.__name__ + ".",
onerror=al_error,
):
procesar(info)
La función recibe el nombre problemático. Registra contexto y decide si continuar.
No ocultes fallos importantes
Una importación fallida puede indicar dependencia ausente, incompatibilidad de plataforma o bug de inicialización.
Clasifica plugins opcionales por separado de componentes obligatorios.
Plugins por prefijo
Un patrón simple busca nombres con un prefijo.
plugins = [
info.name
for info in pkgutil.iter_modules()
if info.name.startswith("miapp_plugin_")
]
Encontrar un nombre no debería importarlo automáticamente.
Prefiere entry points
Los entry points permiten que distribuciones declaren plugins sin escanear todo el entorno.
Los sistemas modernos deberían usar importlib.metadata.entry_points(). El escaneo por nombre sirve en ecosistemas simples o legacy.
Importa solo candidatos seleccionados
Después de descubrir, filtra por configuración, allowlist, versión y política.
import importlib
modulo = importlib.import_module(nombre_plugin)
Importa en un boundary donde puedas manejar fallos y side effects.
get_importer
pkgutil.get_importer(path_item) devuelve el finder asociado.
importer = pkgutil.get_importer("/opt/app/plugins")
print(importer)
Ayuda a diagnosticar si un path se interpreta como directorio, ZIP o hook custom.
Caches de importers
El sistema mantiene caches relacionados con sys.path. Cuando cambien paths, usa APIs públicas de invalidación de importlib.
No modifiques diccionarios internos directamente.
iter_importers
iter_importers(fullname="") produce finders que pueden participar en la búsqueda.
for finder in pkgutil.iter_importers("mi_paquete.modulo"):
print(finder)
Determinar importers de un submódulo puede requerir importar el paquete padre.
Meta path y path hooks
Python utiliza sys.meta_path, sys.path_hooks y caches. pkgutil da acceso cómodo a partes de esta infraestructura.
Para implementar loaders modernos, usa importlib.abc y importlib.machinery.
Lee recursos con get_data
pkgutil.get_data(package, resource) solicita bytes mediante el loader.
datos = pkgutil.get_data(
"mi_paquete",
"datos/config.json",
)
Puede devolver None cuando el recurso no está disponible.
Los recursos son bytes
Decodifica texto explícitamente.
if datos is None:
raise FileNotFoundError("recurso ausente")
texto = datos.decode("utf-8")
Valida tamaño y formato.
Prefiere importlib.resources
El código nuevo debería usar importlib.resources, que ofrece una API más rica y maneja recursos sin archivo físico.
Ese módulo es el penúltimo tema de este lote.
No construyas paths desde __file__
Un paquete puede venir de ZIP, ejecutable frozen o loader virtual. Path(__file__).parent / recurso no siempre funciona.
Las APIs de recursos abstraen el almacenamiento.
extend_path
extend_path(path, name) soporta el modelo legacy de un paquete repartido en varios directorios.
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
Aparece en namespace packages antiguos.
Namespace packages modernos
PEP 420 permite namespaces sin __init__.py. Los proyectos nuevos deberían preferir ese modelo y configurar packaging correctamente.
No añadas extend_path por costumbre.
Archivos .pkg
El mecanismo legacy puede leer archivos .pkg que amplían paths.
Trátalos como configuración sensible, porque nuevos paths cambian qué módulos se importan.
Path hijacking
Directorios escribibles por usuarios al principio de sys.path permiten que un módulo inesperado oculte una dependencia legítima.
Valida roots, ownership y permisos.
Los nombres no son identidades
El mismo nombre puede resolver a lugares diferentes según el orden.
Registra origen, distribución y versión antes de habilitar un plugin.
Distribución y módulo
Un paquete importable no necesariamente comparte nombre con su distribución.
Usa importlib.metadata.packages_distributions() para mapear top-level modules.
Entornos virtuales
Ejecuta la exploración en el venv correcto. El Python global puede mostrar otro conjunto.
Registra sys.executable y sys.path.
ZIP y zipapp
iter_modules() puede cooperar con importers que implementan enumeración. No todos los finders custom lo hacen.
Prueba dentro de .pyz. Consulta zipapp en Python.
Finders personalizados
Para que iter_modules() funcione, un finder no estándar debe implementar el protocolo esperado.
Documenta límites y no supongas que todos los módulos virtuales pueden listarse.
Descubrimiento no significa compatibilidad
Un módulo encontrado puede requerir otra versión, sistema, biblioteca nativa o configuración.
Lee metadatos y valida de manera controlada.
Imports lazy
Guardar nombres permite aplazar imports hasta usar la feature.
Mejora startup, pero desplaza fallos. Añade health checks para plugins obligatorios.
Orden determinístico
El orden puede depender del filesystem y finder. Ordena por nombre.
infos = sorted(
pkgutil.iter_modules(paths),
key=lambda item: item.name,
)
Si la prioridad importa, declárala en metadatos.
Nombres duplicados
Varios paths pueden contener el mismo módulo. Deduplica con cuidado y reporta conflictos.
Es mejor fallar claramente que elegir silenciosamente cuando el origen importa.
Evita escanear todo sys.path
Un escaneo amplio puede ser lento e incluir módulos sin relación.
Restringe a roots de plugins o namespaces.
Límites de recursos
Limita cantidad, profundidad, tiempo y tamaño de recursos.
No hagas discovery recursivo ilimitado en directorios arbitrarios.
Aislamiento por proceso
Como walk_packages() puede importar, descubre extensiones de terceros en un proceso separado con permisos reducidos.
Un timeout protege frente a inicializadores bloqueados.
Integración con modulefinder
modulefinder analiza dependencias referenciadas; pkgutil enumera módulos ofrecidos por importers.
Consulta modulefinder en Python.
Integración con importlib.metadata
Después de descubrir un nombre, consulta distribución, versión y entry points sin importarlo.
Un plugin no debería necesitar cargarse solo para informar su versión.
Pruebas
Prueba directorios, ZIP, namespace packages, módulos, paquetes, duplicados, loaders custom, recursos ausentes, error de importación y varios venvs.
Usa fixtures pequeñas y restaura sys.path después de cada test.
Errores comunes
Los fallos frecuentes son recorrer paquetes sin considerar side effects, escanear todo el entorno, confiar en el orden del filesystem, usar extend_path en proyectos nuevos, confundir módulo y distribución, construir recursos con __file__ e importar todos los candidatos.
Conclusión
pkgutil ofrece herramientas para enumerar módulos, recorrer paquetes, inspeccionar importers y leer recursos mediante loaders. Usa iter_modules() para discovery limitado y walk_packages() solo cuando aceptar imports sea apropiado.
Prefiere entry points y APIs modernas en nuevos sistemas, restringe paths y aísla plugins. Consulta la documentación oficial de pkgutil y symtable en Python para analizar nombres sin importar módulos.







