O módulo pkgutil oferece utilitários para trabalhar com o sistema de importação do Python. Ele pode listar módulos disponíveis em um conjunto de paths, percorrer subpacotes, localizar importers, ler recursos por meio de loaders e dar suporte a formas legadas de pacotes de namespace. É útil em descoberta de plugins, ferramentas de inspeção, registries automáticos, CLIs extensíveis e diagnósticos de importação.
Como a importação do Python é dinâmica, a descoberta precisa ser tratada com cuidado. Percorrer pacotes pode executar código de inicialização, loaders personalizados podem ter efeitos próprios e um nome encontrado não significa que o módulo seja seguro ou compatível. Prefira APIs modernas de importlib quando elas cobrirem o caso, mas entenda pkgutil porque ele ainda aparece em muitas bibliotecas.
Liste módulos com iter_modules
pkgutil.iter_modules() produz informações sobre módulos encontrados em paths.
import pkgutil
for info in pkgutil.iter_modules():
print(info.name, info.ispkg)
Sem um path explícito, a função considera a busca de módulos de nível superior no ambiente atual.
ModuleInfo
Cada resultado contém o finder, o nome do módulo e um indicador de pacote.
for info in pkgutil.iter_modules():
print(info.module_finder)
print(info.name)
print(info.ispkg)
O finder pode ser um objeto do sistema de importação com comportamento específico para diretórios, ZIPs ou loaders customizados.
Descubra dentro de um pacote
Para listar filhos, passe o __path__ do pacote e um prefixo.
import meu_pacote
import pkgutil
for info in pkgutil.iter_modules(
meu_pacote.__path__,
meu_pacote.__name__ + ".",
):
print(info.name)
O prefixo produz nomes importáveis completos.
Nem todo módulo é pacote
O campo ispkg ajuda a distinguir um pacote que pode conter filhos de um módulo simples.
Não use apenas a extensão do arquivo. Loaders e módulos frozen podem não corresponder a um arquivo Python normal.
walk_packages
walk_packages() percorre recursivamente os pacotes encontrados.
for info in pkgutil.walk_packages(
meu_pacote.__path__,
meu_pacote.__name__ + ".",
):
print(info.name)
Essa operação é mais poderosa e mais arriscada que iter_modules().
walk_packages pode importar pacotes
Para obter o __path__ de subpacotes, a função pode importá-los. O código de __init__.py pode executar registros, abrir conexões, ler variáveis e produzir outros side effects.
Não percorra uma árvore não confiável dentro de um processo privilegiado.
O parâmetro onerror
walk_packages() aceita uma função chamada quando um import falha.
def ao_erro(nome):
erros.append(nome)
for info in pkgutil.walk_packages(
pacote.__path__,
pacote.__name__ + ".",
onerror=ao_erro,
):
processar(info)
A função recebe o nome do pacote problemático. Registre contexto e decida se a descoberta deve continuar.
Não esconda erros importantes
Uma falha de import pode significar dependência ausente, incompatibilidade de plataforma ou bug de inicialização.
Classifique plugins opcionais separadamente de componentes obrigatórios.
Descoberta de plugins
Um padrão simples procura módulos com um prefixo.
plugins = [
info.name
for info in pkgutil.iter_modules()
if info.name.startswith("minhaapp_plugin_")
]
Descobrir um nome não deve importá-lo automaticamente sem validação e política.
Prefira entry points para plugins instalados
Entry points permitem que distribuições declarem plugins sem varrer todo o ambiente.
Use importlib.metadata.entry_points() para ecossistemas modernos. A varredura por nome ainda pode ser útil em sistemas simples ou legados.
Importe somente depois de selecionar
Depois de descobrir candidatos, filtre allowlists, configuração e versão antes de importar.
import importlib
modulo = importlib.import_module(nome_plugin)
Faça a importação em um boundary onde erros e side effects possam ser tratados.
get_importer
pkgutil.get_importer(path_item) retorna o finder/importer associado a uma entrada de path.
importer = pkgutil.get_importer("/opt/app/plugins")
print(importer)
Isso ajuda a diagnosticar por que um path é interpretado por um diretório, ZIP ou hook personalizado.
Cache de importers
O sistema de importação mantém caches relacionados a entradas de sys.path. Mudanças em diretórios e hooks podem exigir invalidação por APIs de importlib.
Não modifique caches internos diretamente.
iter_importers
iter_importers(fullname="") percorre finders que podem participar da busca por um módulo.
for finder in pkgutil.iter_importers("meu_pacote.modulo"):
print(finder)
Em alguns casos, determinar importers de submódulos exige importar o pacote pai.
Meta path e path hooks
O sistema de importação usa sys.meta_path, sys.path_hooks e caches. pkgutil oferece uma visão conveniente sobre parte dessa infraestrutura.
Para implementar um loader moderno, use as abstrações de importlib.abc e importlib.machinery.
get_data
pkgutil.get_data(package, resource) solicita bytes de um recurso por meio do loader do pacote.
dados = pkgutil.get_data(
"meu_pacote",
"dados/config.json",
)
O retorno pode ser None quando o recurso não está disponível.
Recursos são bytes
Decodifique texto explicitamente.
if dados is None:
raise FileNotFoundError("recurso ausente")
texto = dados.decode("utf-8")
Valide formato e tamanho antes de processar.
Prefira importlib.resources
Para código novo, importlib.resources possui uma API mais expressiva e funciona bem com recursos que não são arquivos físicos.
Esse módulo será o penúltimo tema deste lote.
Não monte paths com __file__
Um pacote pode estar em ZIP, frozen ou sob loader virtual. Construir Path(__file__).parent / recurso nem sempre funciona.
Use APIs de recursos para abstrair a origem.
extend_path
extend_path(path, name) suporta um modelo legado de pacotes distribuídos por vários diretórios.
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
Esse padrão aparece em pacotes antigos de namespace.
Namespace packages modernos
PEP 420 permite namespace packages sem __init__.py. Para projetos novos, prefira o mecanismo moderno e uma configuração de packaging adequada.
Não adicione extend_path apenas por hábito.
Arquivos .pkg
O mecanismo legado pode considerar arquivos de configuração com extensão .pkg para estender paths.
Trate essas entradas como configuração sensível. Paths adicionais podem alterar quais módulos são importados.
Path hijacking
Adicionar diretórios graváveis por usuários ao início de sys.path permite que um módulo mal posicionado oculte dependências legítimas.
Valide roots de plugin, ownership e permissões antes da descoberta.
Nomes não são identidades
O mesmo nome pode ser encontrado em locais diferentes conforme a ordem de busca.
Registre origem, distribuição e versão antes de ativar um plugin.
Distribuição versus módulo
Um pacote importável não é necessariamente o nome da distribuição instalada.
Use importlib.metadata.packages_distributions() para mapear módulos de topo a distribuições.
Ambientes virtuais
Execute a descoberta dentro do venv correto. A lista de módulos do Python global pode ser completamente diferente.
Registre sys.executable e sys.path em diagnósticos.
ZIP e zipapp
iter_modules() pode cooperar com importers que implementam a extensão necessária. Nem todo finder customizado oferece listagem.
Teste descoberta em arquivos .pyz. Veja zipapp no Python.
Finders customizados
Para que iter_modules() funcione com um finder não padrão, ele precisa fornecer o protocolo esperado.
Documente limites do loader e não presuma que todos os módulos virtuais podem ser enumerados.
Descoberta não valida compatibilidade
Um módulo encontrado pode exigir outra versão, sistema, dependência nativa ou configuração.
Leia metadados e execute uma validação controlada antes de habilitar.
Importação preguiçosa
Guardar apenas nomes permite adiar imports até que o recurso seja necessário.
Isso reduz startup, mas desloca falhas para mais tarde. Ofereça uma etapa de health check que valide plugins obrigatórios.
Ordem determinística
A ordem retornada pode depender de filesystem e finder. Ordene por nome quando precisar de comportamento reproduzível.
infos = sorted(
pkgutil.iter_modules(paths),
key=lambda item: item.name,
)
Se prioridade fizer parte da política, declare-a em metadados em vez de depender da ordem de descoberta.
Duplicatas
Vários paths podem conter o mesmo nome. Deduplicate cuidadosamente e registre conflitos.
Falhar com uma mensagem clara é melhor que escolher silenciosamente quando a origem importa.
Escaneamento amplo
Varrer todo o sys.path pode ser lento e revelar módulos sem relação com a aplicação.
Restrinja a paths de plugin ou namespaces específicos.
Limites
Defina quantidade máxima de entradas, profundidade, tempo e tamanho dos recursos lidos.
Não faça descoberta recursiva ilimitada em diretórios enviados por usuários.
Isolamento
Como walk_packages() pode importar pacotes, execute descoberta de terceiros em processo separado com permissões reduzidas.
Um timeout protege contra inicializadores que travam.
Integração com modulefinder
modulefinder tenta descobrir dependências a partir do código; pkgutil enumera módulos oferecidos por importers.
Veja modulefinder no Python.
Integração com importlib.metadata
Depois de descobrir um nome, obtenha distribuição, versão e entry points com metadados instalados.
Não importe um plugin apenas para perguntar sua versão.
Testes
Teste diretórios, ZIPs, namespace packages, módulos simples, pacotes, duplicatas, loaders customizados, recursos ausentes, erro durante import e diferentes venvs.
Use pacotes de fixture pequenos e restaure sys.path depois de cada teste.
Erros comuns
Os erros mais frequentes são percorrer pacotes sem considerar side effects, varrer todo o ambiente, confiar na ordem do filesystem, usar extend_path em projetos novos, confundir módulo e distribuição, construir recurso com __file__ e importar todo candidato automaticamente.
Conclusão
pkgutil reúne ferramentas práticas para enumerar módulos, percorrer pacotes, localizar importers e ler recursos por loaders. Use iter_modules() para descoberta limitada e walk_packages() apenas quando aceitar as importações necessárias.
Prefira entry points e APIs modernas de importlib em novos sistemas, restrinja paths e isole plugins. Consulte a documentação oficial de pkgutil e symtable no Python para análise de nomes sem importar módulos.







