pkgutil en Python: descubre paquetes

Publicado el: 27/08/2026
Tempo de leitura: 6 minutos
Close-up of a python snake coiled in darkness, showcasing its scales and eyes.

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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    runpy en Python: ejecuta módulos y scripts

    Aprende runpy en Python para ejecutar módulos y scripts, controlar __main__, run_path, alter_sys, namespaces, tests y aislamiento.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Colorful stacked shipping containers at Hamburg port, showcasing global trade and logistics.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    modulefinder en Python: descubre imports

    Aprende modulefinder en Python para descubrir imports, dependencias transitivas, módulos ausentes, paths, plugins y límites del análisis estático.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Person sorting documents in folders outdoors, hands visible, neutral tone.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    py_compile en Python: compila un archivo

    Aprende py_compile en Python para compilar un archivo, controlar .pyc, filenames lógicos, optimización, invalidación por hash y errores.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    compileall en Python: genera bytecode .pyc

    Aprende compileall en Python para generar .pyc, validar sintaxis, compilar en paralelo y controlar optimización, paths y builds reproducibles.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Vivid close-up of code on a computer screen showcasing programming details.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    codeop en Python: compila entrada interactiva

    Aprende codeop en Python para detectar comandos completos, incompletos o inválidos, crear REPLs y conservar flags de __future__ de forma

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    linecache en Python: lee líneas del código

    Aprende linecache en Python para recuperar líneas de código, actualizar cache, soportar tracebacks y loaders, conservar indentación y proteger rutas.

    Ler mais

    Tempo de leitura: 8 minutos
    27/08/2026