pkgutil en Python: descubre paquetes

Publicado el: 13/08/2026
Tempo de leitura: 5 minutos
Paquete de software que representa descubrimiento de módulos con pkgutil en Python

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,
):
    pass

Sin 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 encontrados

Prueba también fallos de import, namespace packages, ZIPs e inicializadores con efectos.

Errores frecuentes

  • Recorrer todo sys.path sin 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python en pantalla que representa inspección de módulos y paquetes
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    inspect.ispackage: identifica paquetes Python

    Aprende inspect.ispackage en Python para identificar paquetes, explorar módulos y crear herramientas de introspección seguras.

    Ler mais

    Tempo de leitura: 5 minutos
    05/10/2026