Listar todos os módulos que fazem parte de um pacote python?

107

Existe uma maneira direta de encontrar todos os módulos que fazem parte de um pacote python? Eu encontrei essa discussão antiga , que não é realmente conclusiva, mas adoraria ter uma resposta definitiva antes de lançar minha própria solução com base em os.listdir ().

static_rtti
fonte
6
@ S.Lott: Existem soluções mais gerais disponíveis, os pacotes python nem sempre estão em diretórios no sistema de arquivos, mas também podem estar dentro de zips.
u0b34a0f6ae
4
por que reinventar a roda? Se o python adquirir hipermódulos no Python 4, pkgutil e atualizado com ele, meu código ainda funcionará. Gosto de usar abstrações disponíveis. Use o método óbvio fornecido, ele é testado e funciona. Reimplementando isso ... agora você tem que encontrar e trabalhar em cada caso de esquina sozinho.
u0b34a0f6ae
1
@ S.Lott: Então, toda vez que o aplicativo é iniciado, ele descompacta seu próprio ovo, se instalado em um, apenas para verificar isso? Envie um patch para o meu projeto para reinventar a roda nesta função: git.gnome.org/cgit/kupfer/tree/kupfer/plugins.py#n17 . Por favor, considere os diretórios eggs e normais, não exceda 20 linhas.
u0b34a0f6ae
1
@ S.Lott: Por que você não entende que é relevante é algo que você não consegue entender. Descobrir isso programaticamente significa que o aplicativo se interessa pelo conteúdo de um pacote, não pelo usuário.
u0b34a0f6ae
3
Claro que quero dizer programaticamente! Caso contrário, eu não teria mencionado "lançando minha própria solução com os.listdir ()"
static_rtti

Respostas:

145

Sim, você quer algo baseado em pkgutilou semelhante - desta forma, você pode tratar todos os pacotes da mesma forma, independentemente se eles estão em ovos ou zips ou algo assim (onde os.listdir não ajudará).

import pkgutil

# this is the package we are inspecting -- for example 'email' from stdlib
import email

package = email
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
    print "Found submodule %s (is a package: %s)" % (modname, ispkg)

Como importá-los também? Você pode usar __import__normalmente:

import pkgutil

# this is the package we are inspecting -- for example 'email' from stdlib
import email

package = email
prefix = package.__name__ + "."
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__, prefix):
    print "Found submodule %s (is a package: %s)" % (modname, ispkg)
    module = __import__(modname, fromlist="dummy")
    print "Imported", module
u0b34a0f6ae
fonte
9
pelo que isso é importerretornado pkgutil.iter_modules? Posso usá-lo para importar um módulo em vez de usar este "hack" aparentemente __import__(modname, fromlist="dummy")?
MestreLion
29
Consegui usar o importador assim: m = importer.find_module(modname).load_module(modname)e então mé o módulo, por exemplo:m.myfunc()
chrisleague
@chrisleague Eu estava usando seu método com o python 2.7, mas agora preciso continuar com o python 3.4, então você sabe que no python 3 pkutil.iter_modules produz (module_finder, name, ispkg) em vez de (module_loader, name, ispkg). O que posso fazer para que funcione como o anterior?
crax de
Seu primeiro exemplo produz o seguinte erro: "AttributeError: objeto 'módulo' não tem atributo ' _path_ '" Isso tem algo a ver com a versão do Python? (Eu uso Python 2.7)
Apostolos
@Apostolos, você está usando apenas um sublinhado em cada lado do caminho (ou seja _path_). Deve haver dois de cada lado, para um total de quatro (ou seja __path__).
therealmitchconnors
46

A ferramenta certa para esse trabalho é pkgutil.walk_packages.

Para listar todos os módulos em seu sistema:

import pkgutil
for importer, modname, ispkg in pkgutil.walk_packages(path=None, onerror=lambda x: None):
    print(modname)

Esteja ciente de que walk_packages importa todos os subpacotes, mas não os submódulos.

Se você deseja listar todos os submódulos de um determinado pacote, você pode usar algo assim:

import pkgutil
import scipy
package=scipy
for importer, modname, ispkg in pkgutil.walk_packages(path=package.__path__,
                                                      prefix=package.__name__+'.',
                                                      onerror=lambda x: None):
    print(modname)

iter_modules lista apenas os módulos que têm um nível de profundidade. walk_packages obtém todos os submódulos. No caso de scipy, por exemplo, walk_packages retorna

scipy.stats.stats

enquanto iter_modules só retorna

scipy.stats

A documentação do pkgutil ( http://docs.python.org/library/pkgutil.html ) não lista todas as funções interessantes definidas em /usr/lib/python2.6/pkgutil.py.

Talvez isso signifique que as funções não fazem parte da interface "pública" e estão sujeitas a alterações.

No entanto, pelo menos a partir do Python 2.6 (e talvez nas versões anteriores?), O pkgutil vem com um método walk_packages que percorre recursivamente todos os módulos disponíveis.

unutbu
fonte
5
walk_packagesagora está na documentação: docs.python.org/library/pkgutil.html#pkgutil.walk_packages
Caracol mecânico
1
Seu segundo exemplo produz o seguinte erro: "AttributeError: objeto 'módulo' não tem atributo ' _path_ '" - Eu não testei com 'scipy', mas com alguns outros pacotes. Isso tem alguma coisa a ver com a versão do Python? (Eu uso Python 2.7)
Apostolos
1
@Apostolos: Deve haver dois sublinhados ( _) antes e depois path- ou seja, use empackage.__path__ vez de package._path_. Pode ser mais fácil tentar cortar e colar o código em vez de redigitá-lo.
unutbu
Haviam dois deles quando escrevi o comentário! :) Mas eles foram removidos pelo sistema. Foi mal; Eu deveria ter colocado três undercores. Mas então, não haveria problema se eu quisesse usar itálico, o que não fiz! ... É uma situação de perda-perda. :) Enfim, quando executei o código usei dois deles, é claro. (Copiei e colei o código.)
Apostolos
@Apostolos: Certifique-se de que a variável packageestá apontando para um pacote, não um módulo. Módulos são arquivos enquanto pacotes são diretórios. Todos os pacotes têm o __path__atributo (... a menos que alguém
exclua
2

Isso funciona para mim:

import types

for key, obj in nltk.__dict__.iteritems():
    if type(obj) is types.ModuleType: 
        print key
DarinP
fonte
1
Isso falha de duas maneiras 1. os pacotes nem sempre importam explicitamente seus submódulos para o namespace de nível superior 2. Os pacotes podem importar outros módulos de terceiros para seu namespace de nível superior
wim
0

Eu estava procurando uma maneira de recarregar todos os submódulos que estou editando ao vivo em meu pacote. É uma combinação das respostas / comentários acima, então decidi postar aqui como uma resposta ao invés de um comentário.

package=yourPackageName
import importlib
import pkgutil
for importer, modname, ispkg in pkgutil.walk_packages(path=package.__path__, prefix=package.__name__+'.', onerror=lambda x: None):
    try:
        modulesource = importlib.import_module(modname)
        reload(modulesource)
        print("reloaded: {}".format(modname))
    except Exception as e:
        print('Could not load {} {}'.format(modname, e))
user1767754
fonte
-4

Aqui está uma maneira, de início:

>>> import os
>>> filter(lambda i: type(i) == type(os), [getattr(os, j) for j in dir(os)])
[<module 'UserDict' from '/usr/lib/python2.5/UserDict.pyc'>, <module 'copy_reg' from '/usr/lib/python2.5/copy_reg.pyc'>, <module 'errno' (built-in)>, <module 'posixpath' from '/usr/lib/python2.5/posixpath.pyc'>, <module 'sys' (built-in)>]

Certamente poderia ser limpo e melhorado.

EDIT: Aqui está uma versão um pouco melhor:

>>> [m[1] for m in filter(lambda a: type(a[1]) == type(os), os.__dict__.items())]
[<module 'copy_reg' from '/usr/lib/python2.5/copy_reg.pyc'>, <module 'UserDict' from '/usr/lib/python2.5/UserDict.pyc'>, <module 'posixpath' from '/usr/lib/python2.5/posixpath.pyc'>, <module 'errno' (built-in)>, <module 'sys' (built-in)>]
>>> [m[0] for m in filter(lambda a: type(a[1]) == type(os), os.__dict__.items())]
['_copy_reg', 'UserDict', 'path', 'errno', 'sys']

NOTA: Isso também encontrará módulos que podem não estar necessariamente localizados em um subdiretório do pacote, se eles forem puxados em seu __init__.pyarquivo, portanto, depende do que você entende por "parte de" um pacote.

Steve Losh
fonte
desculpe, isso não tem uso. Além dos falsos positivos, ele também encontrará apenas submódulos de pacotes já importados.
u0b34a0f6ae