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

    Red de código binario que representa el grafo de imports analizado con modulefinder en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    modulefinder en Python: analiza imports

    Aprende modulefinder en Python para mapear imports, detectar módulos ausentes, personalizar rutas y auditar dependencias con límites claros.

    Ler mais

    Tempo de leitura: 6 minutos
    13/08/2026
    Archivadores organizados que representan aplicaciones empaquetadas en archivos .pyz con zipapp en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zipapp en Python: crea ejecutables .pyz

    Aprende zipapp en Python para empaquetar aplicaciones en archivos .pyz, definir entry points, incluir dependencias y distribuir con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    13/08/2026
    Editor de código que representa autocompletado de REPL con rlcompleter en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    rlcompleter en Python: autocompletar REPL

    Aprende rlcompleter en Python para añadir autocompletado a REPLs, consolas y editores, controlar namespaces y evitar efectos secundarios.

    Ler mais

    Tempo de leitura: 6 minutos
    13/08/2026
    Ventana de terminal que representa una consola interactiva creada con cmd en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    cmd en Python: crea consolas interactivas

    Aprende cmd en Python para crear consolas interactivas con comandos, ayuda, historial, autocompletado, pruebas y control seguro de acciones.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Terminal interactivo que representa un REPL personalizado creado con el módulo code en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    code en Python: crea un REPL personalizado

    Aprende el módulo code en Python para crear REPLs personalizados, controlar namespaces, prompts, salida, bloques incompletos y cierre local.

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Código de aplicación web que representa WSGI con wsgiref en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    wsgiref en Python: aplicaciones WSGI

    Aprende wsgiref en Python para crear y validar aplicaciones WSGI, probar environ y headers, enrutar solicitudes y ejecutar un servidor

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026