pkgutil no Python: descubra pacotes

Publicado em: 13/08/2026
Tempo de leitura: 6 minutos
Pacote de software representando descoberta de módulos com pkgutil no Python

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 encontrados

Teste erros de import, namespace packages, ZIPs e objetos com efeitos colaterais em __init__.py.

Erros frequentes

  • Percorrer todo sys.path sem 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Rede de código binário representando o grafo de imports analisado com modulefinder no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    modulefinder no Python: analise imports

    Aprenda modulefinder no Python para mapear imports, detectar módulos ausentes, personalizar caminhos e auditar dependências com limites claros.

    Ler mais

    Tempo de leitura: 7 minutos
    13/08/2026
    Pastas organizadas representando aplicações empacotadas em arquivos .pyz com zipapp no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    zipapp no Python: crie executáveis .pyz

    Aprenda zipapp no Python para empacotar aplicações em arquivos .pyz, definir entry points, incluir dependências e distribuir com segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    13/08/2026
    Editor de código representando autocompletar em REPL com rlcompleter no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    rlcompleter no Python: autocompletar REPL

    Aprenda rlcompleter no Python para adicionar autocompletar a REPLs, consoles e editores, controlar namespaces e evitar efeitos colaterais.

    Ler mais

    Tempo de leitura: 6 minutos
    13/08/2026
    Janela de terminal representando console interativo criado com cmd no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    cmd no Python: crie consoles interativos

    Aprenda cmd no Python para criar consoles interativos com comandos, ajuda, histórico, autocompletar, testes e controle seguro de ações.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Terminal interativo representando um REPL customizado com o módulo code no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    code no Python: crie um REPL customizado

    Aprenda o módulo code no Python para criar REPLs customizados, controlar namespaces, prompts, saída, blocos incompletos e encerramento seguro.

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Código de aplicação web representando WSGI com wsgiref no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    wsgiref no Python: aplicações WSGI

    Aprenda wsgiref no Python para criar e validar aplicações WSGI, testar environ, headers, rotas e servidores locais sem usar em

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026