O módulo pkgutil no Python reúne utilitários para trabalhar com o sistema de imports e com pacotes. Ele pode listar módulos disponíveis, percorrer subpacotes, encontrar importers, resolver um nome textual para um objeto e estender o caminho de um pacote distribuído em vários diretórios.
Essas funções são úteis em sistemas de plugins, ferramentas de diagnóstico, geradores de documentação e auditorias. O uso exige cautela: algumas operações importam pacotes para descobrir seus submódulos, e importar um pacote pode executar código de nível superior. Também há APIs legadas de recursos que aceitam caminhos e devem receber apenas entrada confiável.
O que é ModuleInfo
pkgutil.ModuleInfo é uma namedtuple com três campos: o finder responsável, o nome do módulo e um booleano que indica se ele é pacote.
from pkgutil import iter_modules
for info in iter_modules():
print(info.name, info.ispkg, info.module_finder)O objeto fornece um resumo, não importa o módulo automaticamente. Isso torna iter_modules() apropriado para listagens rápidas.
Listar módulos de primeiro nível
Sem caminho, iter_modules() examina os módulos visíveis em sys.path.
import pkgutil
nomes = sorted(info.name for info in pkgutil.iter_modules())
print(nomes[:20])O resultado depende do ambiente virtual, da instalação do Python e de caminhos personalizados. Registre esse contexto quando gerar relatórios. O artigo sobre sysconfig no Python ajuda a identificar diretórios de instalação.
Listar submódulos de um pacote
Para limitar a busca a um pacote, passe seu __path__ e um prefixo.
import pkgutil
import meu_app
for info in pkgutil.iter_modules(
meu_app.__path__,
meu_app.__name__ + '.',
):
print(info.name)O prefixo é importante para retornar nomes totalmente qualificados, como meu_app.plugins.csv.
Busca recursiva com walk_packages()
walk_packages() percorre recursivamente. Para descobrir o __path__ de cada pacote, ele precisa importá-lo.
import pkgutil
import meu_app
for info in pkgutil.walk_packages(
meu_app.__path__,
meu_app.__name__ + '.',
):
print(info.name, info.ispkg)Esse efeito colateral é relevante. Um __init__.py pode abrir conexões, ler configuração, registrar handlers ou falhar por dependências externas. Não percorra pacotes desconhecidos dentro de um processo privilegiado.
Tratar erros durante a caminhada
O parâmetro onerror recebe o nome de um pacote que falhou ao importar.
erros = []
def registrar_erro(nome):
erros.append(nome)
for info in pkgutil.walk_packages(
meu_app.__path__,
meu_app.__name__ + '.',
onerror=registrar_erro,
):
...Sem callback, ImportError é ignorado, enquanto outras exceções normalmente interrompem a busca. Registre falhas sem esconder problemas obrigatórios.
Descoberta segura de plugins
Uma aplicação pode definir uma raiz específica para plugins.
import pkgutil
import meu_app.plugins
permitidos = []
for info in pkgutil.iter_modules(
meu_app.plugins.__path__,
'meu_app.plugins.',
):
if info.name.rsplit('.', 1)[-1].isidentifier():
permitidos.append(info.name)Descobrir um nome não significa autorizá-lo. Mantenha uma allowlist, valide metadados, versões e assinaturas, e importe somente depois da decisão.
Encontrar importers
get_importer(path_item) recupera o finder associado a um item de caminho. O resultado pode ser armazenado em sys.path_importer_cache.
import pkgutil
finder = pkgutil.get_importer('/projeto/plugins')
print(type(finder).__name__)Se hooks de caminho mudarem em runtime, talvez seja necessário invalidar o cache correspondente. Alterar import hooks é uma operação global e deve ser evitada em bibliotecas comuns.
Iterar importers
iter_importers(fullname) retorna finders capazes de procurar determinado nome.
import pkgutil
for finder in pkgutil.iter_importers('meu_app.plugins.csv'):
print(finder)Se o nome pertence a um pacote, a função pode importar o pacote pai como efeito colateral. Aplique as mesmas precauções usadas com walk_packages().
Estender o caminho de um pacote
extend_path() é uma técnica histórica para distribuir partes de um pacote lógico em diretórios diferentes.
# meu_namespace/__init__.py
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)Hoje, namespace packages nativos geralmente são preferíveis quando não há necessidade de __init__.py. Ainda assim, projetos legados podem depender desse padrão.
Arquivos .pkg e confiança
extend_path() também lê arquivos *.pkg correspondentes ao nome do pacote. As entradas são aceitas como verdade, mesmo quando o caminho ainda não existe.
Por isso, trate arquivos .pkg como configuração confiável. Quem consegue alterá-los pode influenciar locais de import. Proteja permissões e não gere conteúdo a partir de entrada externa.
Resolver objetos por nome
resolve_name() transforma uma string em módulo, classe, função ou atributo.
from pkgutil import resolve_name
funcao = resolve_name('meu_app.tarefas:executar')
funcao()A forma com dois pontos é mais clara: à esquerda está o módulo importado; à direita, a hierarquia de objetos. Sem dois pontos, a função precisa tentar importações repetidas para descobrir onde termina o pacote.
Validar nomes resolvidos
Resolver um nome importa código e devolve um objeto arbitrário. Não aceite uma string fornecida por usuário sem restrições.
ALVOS = {
'relatorio': 'meu_app.tarefas:gerar_relatorio',
'limpeza': 'meu_app.tarefas:limpar_temporarios',
}
nome = ALVOS[acao]
funcao = resolve_name(nome)Depois de resolver, confirme que o objeto é callable e que possui a interface esperada.
get_data() para recursos
pkgutil.get_data(package, resource) lê bytes por meio do loader.
import pkgutil
dados = pkgutil.get_data(
'meu_app',
'dados/config.json',
)
if dados is None:
raise FileNotFoundError('Recurso indisponível')A API funciona com loaders que implementam get_data, inclusive alguns pacotes em ZIP. Namespace packages podem não ser suportados.
Risco de path traversal em get_data()
A documentação alerta que get_data() é destinado a entrada confiável. Caminhos com ../ ou absolutos podem alcançar arquivos que não pertencem ao recurso esperado, dependendo do loader.
RECURSOS = {
'padrao': 'dados/padrao.json',
'tema': 'dados/tema.css',
}
conteudo = pkgutil.get_data('meu_app', RECURSOS[chave])Use nomes conhecidos, extensões permitidas e uma tabela fixa. Para acesso estruturado moderno, prefira importlib.resources no Python.
pkgutil ou importlib.resources?
pkgutil.get_data() permanece útil em código legado e loaders compatíveis. importlib.resources oferece objetos Traversable, leitura de texto, diretórios e contextos temporários com uma API mais explícita.
Em código novo, use importlib.resources. Mantenha pkgutil quando compatibilidade com APIs antigas é uma exigência real.
Descoberta não é carregamento
iter_modules() lista candidatos sem importar todos os módulos. walk_packages() importa pacotes, mas não necessariamente cada módulo final.
Separe as fases: descobrir, validar, autorizar e somente então importar. Essa arquitetura reduz efeitos colaterais e facilita auditoria.
Combinar com modulefinder
modulefinder no Python parte de um script e segue imports. pkgutil parte de caminhos e lista o que está disponível. As perspectivas se complementam.
Um plugin pode estar instalado e aparecer em pkgutil, mas nunca ser importado pelo script. Outro módulo pode ser importado dinamicamente e exigir um manifesto explícito.
Combinar com zipapp
iter_modules() possui suporte para finders comuns e zipimporter. Isso permite descobrir módulos dentro de aplicações zipapp, desde que o finder implemente a interface necessária.
Teste o artefato empacotado, pois o comportamento pode diferir do diretório de origem.
Cache e mudanças em runtime
Se você instalar plugins depois de iniciar o processo, invalide caches de import e repita a descoberta de forma controlada.
import importlib
import sys
importlib.invalidate_caches()
sys.path_importer_cache.pop('/projeto/plugins', None)Evite instalações concorrentes enquanto outros threads importam módulos. Prefira reiniciar workers após atualizar plugins.
Desempenho
Listar todos os módulos em sys.path pode ser caro. Limite a busca ao __path__ de um pacote conhecido e aplique um prefixo.
Para interfaces interativas, faça cache de resultados por versão do ambiente e invalide após atualizações.
Testar a descoberta
Crie um pacote temporário com módulos e subpacotes conhecidos.
def test_lista_plugin(tmp_path, monkeypatch):
raiz = tmp_path / 'plugins'
raiz.mkdir()
(raiz / 'alpha.py').write_text('NOME = "alpha"\n')
encontrados = [
info.name
for info in pkgutil.iter_modules([str(raiz)])
]
assert 'alpha' in encontradosTeste erros de import, namespace packages, ZIPs e objetos com efeitos colaterais em __init__.py.
Erros frequentes
- Percorrer todo
sys.pathsem necessidade. - Ignorar que
walk_packages()importa pacotes. - Confiar em qualquer nome descoberto como plugin.
- Resolver objetos fornecidos pelo usuário.
- Passar caminhos livres a
get_data(). - Tratar filtro de underscore como segurança.
- Esquecer caches após instalar plugins.
Boas práticas
- Restrinja a busca a pacotes conhecidos.
- Separe descoberta, validação e import.
- Use allowlists para plugins e objetos.
- Prefira importlib.resources em código novo.
- Proteja arquivos
.pkg. - Registre erros de import com contexto.
- Reinicie workers após mudanças importantes.
Conclusão
O pkgutil no Python oferece ferramentas práticas para listar módulos, percorrer pacotes, consultar finders, resolver objetos e manter compatibilidade com pacotes distribuídos.
Use essas APIs com consciência dos efeitos colaterais. Importar pacotes executa código, resolver nomes carrega objetos e caminhos de recursos podem escapar da área esperada. Consulte a documentação oficial do pkgutil e a referência do sistema de imports.







