El módulo pkgutil en Python reúne utilidades para el sistema de imports y el soporte de paquetes. Permite listar módulos disponibles, recorrer subpaquetes, consultar importers, resolver un nombre textual a un objeto y extender la ruta de un paquete distribuido en varios directorios.
Estas funciones son útiles en sistemas de plugins, herramientas de diagnóstico, generadores de documentación y auditorías. También requieren cuidado: algunas operaciones importan paquetes para descubrir sus hijos, y un import ejecuta código de nivel superior. Las APIs antiguas de recursos aceptan rutas y deben recibir únicamente valores confiables.
Qué es ModuleInfo
pkgutil.ModuleInfo es una namedtuple con el finder, el nombre del módulo y un booleano que indica si es paquete.
from pkgutil import iter_modules
for info in iter_modules():
print(info.name, info.ispkg, info.module_finder)El objeto es un resumen. iter_modules() no importa automáticamente todos los módulos descubiertos.
Listar módulos de primer nivel
Sin ruta, iter_modules() examina los módulos visibles en sys.path.
import pkgutil
nombres = sorted(info.name for info in pkgutil.iter_modules())
print(nombres[:20])El resultado depende del entorno virtual, la instalación de Python y rutas personalizadas. Registra ese contexto. sysconfig en Python ayuda a identificar directorios de instalación.
Listar submódulos de un paquete
Pasa el __path__ del paquete y un prefijo.
import pkgutil
import mi_app
for info in pkgutil.iter_modules(
mi_app.__path__,
mi_app.__name__ + '.',
):
print(info.name)El prefijo produce nombres completos como mi_app.plugins.csv.
Recorrer paquetes recursivamente
walk_packages() descubre hijos de forma recursiva. Debe importar paquetes para obtener su __path__.
import pkgutil
import mi_app
for info in pkgutil.walk_packages(
mi_app.__path__,
mi_app.__name__ + '.',
):
print(info.name, info.ispkg)Este efecto es importante. Un __init__.py puede abrir conexiones, leer configuración, registrar handlers o fallar por servicios externos. No recorras paquetes desconocidos en procesos privilegiados.
Tratar errores de la búsqueda
El callback onerror recibe el nombre del paquete que falló al importarse.
errores = []
def registrar_error(nombre):
errores.append(nombre)
for info in pkgutil.walk_packages(
mi_app.__path__,
mi_app.__name__ + '.',
onerror=registrar_error,
):
passSin callback, ImportError suele ignorarse y otras excepciones se propagan. Registra fallos sin ocultar componentes obligatorios.
Descubrir plugins de forma segura
Una aplicación puede reservar un paquete para plugins.
import pkgutil
import mi_app.plugins
candidatos = []
for info in pkgutil.iter_modules(
mi_app.plugins.__path__,
'mi_app.plugins.',
):
hoja = info.name.rsplit('.', 1)[-1]
if hoja.isidentifier():
candidatos.append(info.name)Descubrir no equivale a autorizar. Aplica una allowlist, comprueba metadatos y versiones, e importa solo después de validar.
Consultar importers
get_importer(path_item) devuelve el finder asociado a una entrada de ruta. Los finders nuevos se almacenan en sys.path_importer_cache.
import pkgutil
finder = pkgutil.get_importer('/proyecto/plugins')
print(type(finder).__name__)Si cambian los path hooks, quizá sea necesario invalidar el cache. Las bibliotecas comunes deberían evitar cambios globales del sistema de imports.
Iterar importers
iter_importers(fullname) produce finders capaces de buscar un nombre.
for finder in pkgutil.iter_importers('mi_app.plugins.csv'):
print(finder)Cuando el nombre pertenece a un paquete, el paquete padre puede importarse como efecto secundario.
Extender la ruta de un paquete
extend_path() es un mecanismo histórico para repartir un paquete lógico en varios directorios.
# mi_namespace/__init__.py
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)Los namespace packages nativos suelen ser preferibles en proyectos nuevos, pero ecosistemas antiguos pueden depender de este patrón.
Confianza en archivos .pkg
extend_path() también lee archivos *.pkg coincidentes. Sus entradas se aceptan tal como están, incluso si la ruta no existe.
Trátalos como configuración confiable. Quien pueda modificarlos puede influir en las rutas de import. Protege permisos y no los generes desde entrada externa.
Resolver objetos por nombre
resolve_name() convierte una cadena en módulo, clase, función o atributo.
from pkgutil import resolve_name
funcion = resolve_name('mi_app.tareas:ejecutar')
funcion()La forma con dos puntos es explícita: a la izquierda está el módulo y a la derecha la jerarquía de objetos.
Validar antes de resolver
Resolver un nombre importa código y devuelve un objeto arbitrario. No aceptes destinos proporcionados libremente por usuarios.
DESTINOS = {
'informe': 'mi_app.tareas:generar_informe',
'limpieza': 'mi_app.tareas:limpiar_temporales',
}
funcion = resolve_name(DESTINOS[accion])
if not callable(funcion):
raise TypeError('El destino no es callable')Leer recursos con get_data()
pkgutil.get_data(package, resource) solicita bytes mediante el loader.
import pkgutil
datos = pkgutil.get_data('mi_app', 'datos/config.json')
if datos is None:
raise FileNotFoundError('Recurso no disponible')Funciona con loaders que implementan get_data. Los namespace packages pueden no soportarlo.
Evitar path traversal
La documentación advierte que get_data() está destinado a entrada confiable. Componentes padres y rutas absolutas pueden alcanzar archivos fuera del área esperada, según el loader.
RECURSOS = {
'predeterminado': 'datos/predeterminado.json',
'tema': 'datos/tema.css',
}
contenido = pkgutil.get_data('mi_app', RECURSOS[clave])Usa nombres conocidos y un mapping fijo. Para código nuevo, prefiere importlib.resources en Python.
pkgutil frente a importlib.resources
pkgutil.get_data() sigue siendo útil para código heredado. importlib.resources ofrece objetos Traversable, helpers de texto, directorios y contextos temporales con una API más estructurada.
Elige importlib.resources en implementaciones nuevas salvo que la compatibilidad requiera pkgutil.
Separar descubrimiento y carga
iter_modules() descubre candidatos sin importar todos los módulos. walk_packages() importa paquetes, pero no necesariamente cada módulo final.
Diseña fases separadas: descubrir, validar, autorizar e importar. Esto reduce efectos y mejora la auditoría.
Combinar con modulefinder
modulefinder en Python parte de un script y sigue imports. Pkgutil parte de rutas y lista lo disponible. Las dos perspectivas se complementan.
Un plugin puede estar instalado y ser visible para pkgutil sin que el entry point lo importe. Un plugin dinámico puede necesitar un manifiesto.
Descubrir módulos dentro de zipapps
iter_modules() soporta finders comunes y zipimporter. Puede descubrir módulos dentro de una aplicación zipapp si el finder ofrece la interfaz requerida.
Prueba el artefacto empaquetado, porque el comportamiento puede variar respecto al directorio fuente.
Invalidar caches
Si instalas plugins con el proceso activo, invalida caches y repite la búsqueda de forma controlada.
import importlib
import sys
importlib.invalidate_caches()
sys.path_importer_cache.pop('/proyecto/plugins', None)Evita instalaciones concurrentes mientras otros threads importan módulos. Es preferible reiniciar workers.
Rendimiento
Recorrer todo sys.path puede ser costoso. Limita la búsqueda a un paquete conocido y usa prefijos.
Las interfaces interactivas pueden cachear resultados por versión del entorno e invalidarlos tras actualizaciones.
Probar el descubrimiento
Crea un directorio temporal con módulos conocidos.
def test_lista_plugin(tmp_path):
raiz = tmp_path / 'plugins'
raiz.mkdir()
(raiz / 'alpha.py').write_text('NOMBRE = "alpha"\n')
encontrados = [
info.name
for info in pkgutil.iter_modules([str(raiz)])
]
assert 'alpha' in encontradosPrueba también fallos de import, namespace packages, ZIPs e inicializadores con efectos.
Errores frecuentes
- Recorrer todo
sys.pathsin necesidad. - Ignorar que
walk_packages()importa paquetes. - Confiar en cualquier módulo descubierto.
- Resolver nombres enviados por usuarios.
- Pasar rutas libres a
get_data(). - Tratar underscores como control de acceso.
- Mantener caches obsoletos.
Buenas prácticas
- Limita la búsqueda a paquetes conocidos.
- Separa descubrimiento, validación e import.
- Usa allowlists para plugins y destinos.
- Prefiere importlib.resources en código nuevo.
- Protege archivos
.pkg. - Registra errores de import con contexto.
- Reinicia workers tras cambios importantes.
Conclusión
pkgutil en Python ofrece herramientas prácticas para listar módulos, recorrer paquetes, consultar finders, resolver objetos y mantener compatibilidad con paquetes distribuidos.
Usa estas APIs considerando sus efectos. Importar paquetes ejecuta código, resolver nombres carga objetos y las rutas de recursos pueden escapar de los límites esperados. Consulta la documentación oficial de pkgutil y la referencia del sistema de imports.







