Existe uma maneira padrão de listar nomes de módulos Python em um pacote?

100

Existe uma maneira direta de listar os nomes de todos os módulos em um pacote, sem usar __all__?

Por exemplo, dado este pacote:

/testpkg
/testpkg/__init__.py
/testpkg/modulea.py
/testpkg/moduleb.py

Estou me perguntando se existe uma maneira padrão ou integrada de fazer algo assim:

>>> package_contents("testpkg")
['modulea', 'moduleb']

A abordagem manual seria iterar pelos caminhos de pesquisa do módulo para encontrar o diretório do pacote. Pode-se então listar todos os arquivos nesse diretório, filtrar os arquivos py / pyc / pyo nomeados exclusivamente, remover as extensões e retornar essa lista. Mas isso parece uma quantidade razoável de trabalho para algo que o mecanismo de importação de módulo já está fazendo internamente. Essa funcionalidade está exposta em algum lugar?

DNS
fonte

Respostas:

23

Talvez isso faça o que você está procurando?

import imp
import os
MODULE_EXTENSIONS = ('.py', '.pyc', '.pyo')

def package_contents(package_name):
    file, pathname, description = imp.find_module(package_name)
    if file:
        raise ImportError('Not a package: %r', package_name)
    # Use a set because some may be both source and compiled.
    return set([os.path.splitext(module)[0]
        for module in os.listdir(pathname)
        if module.endswith(MODULE_EXTENSIONS)])
cdleary
fonte
1
Eu adicionaria 'e módulo! = " Init .py"' ​​ao 'if' final, visto que init .py não faz parte do pacote. E .pyo é outra extensão válida. Além disso, usar imp.find_module é realmente uma boa ideia; Acho que essa é a resposta certa.
DNS de
3
Eu discordo - você pode importar o init diretamente, então por que usar um caso especial? Com certeza não é especial o suficiente para quebrar as regras. ;-)
cdleary
6
Você provavelmente deve usar em imp.get_suffixes()vez de sua lista escrita à mão.
itsadok
3
Além disso, observe que isso não funciona em subpacotes comoxml.sax
itsadok
1
Esta é uma maneira muito ruim. Você não pode dizer com segurança o que é um módulo a partir da extensão do nome do arquivo.
wim
188

Usando python2.3 e superior , você também pode usar o pkgutilmódulo:

>>> import pkgutil
>>> [name for _, name, _ in pkgutil.iter_modules(['testpkg'])]
['modulea', 'moduleb']

EDITAR: Observe que o parâmetro não é uma lista de módulos, mas uma lista de caminhos, então você pode querer fazer algo assim:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
jp.
fonte
15
Isso é perturbadoramente indocumentado, mas parece ser a maneira mais correta de fazer isso. Espero que não se importe por eu adicionar a nota.
itsadok
13
pkgutilestá lá em python2.3 e superior, na verdade . Além disso, embora pkgutil.iter_modules()não funcione recursivamente, existe um pkgutil.walk_packages()também, que irá recursivamente. Obrigado pela indicação para este pacote.
Sandip Bhattacharya
Por iter_modulesque não funciona para importação absoluta como a.b.testpkg? Ele está me dando[]
Hussain
Esqueci sua EDITAR :(. Desculpe. Funcionou depois que segui o segundo snippet.
Hussain
1
Não posso confirmar se é pkgutil.walk_packages()recorrente, ele me dá a mesma saída que pkgutil.iter_modules(), então acho que a resposta está incompleta.
primeiro
29
import module
help(module)
Tríptico
fonte
2
Embora a ajuda liste o conteúdo do pacote na parte inferior do texto de ajuda, a questão é mais como fazer isso: f (package_name) => ["module1_name", "module2_name"]. Acho que poderia analisar a string retornada por help, mas isso parece mais indireto do que listar o diretório.
DNS de
1
@DNS: help()imprime coisas, não retorna uma string.
Junuxx de
Eu concordo que esta é uma forma indireta, mas me mandou para uma toca de coelho para ver como help()funciona. De qualquer forma, o built-in pydocmódulo pode ajudar a cuspir o string que help()Pagina: import pydoc; pydoc.render_doc('mypackage').
sraboy
8

Não sei se estou negligenciando algo, ou se as respostas estão apenas desatualizadas, mas;

Conforme declarado pelo usuário815423426, isso só funciona para objetos ativos e os módulos listados são apenas módulos que foram importados antes.

Listar módulos em um pacote parece muito fácil usando inspect :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
fonte
Coloquei importado = import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule) mas o caminho importd é ~ / myproj / __ init .py e m é uma lista com (mymod, '~ /myproj/mymod/__init__.py ')
hithwen
1
@hithwen Não faça perguntas nos comentários, especialmente se não estiverem diretamente relacionados. Ser um bom samaritano: use imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__importa o módulo de nível superior, consulte a documentação .
siebz0r
Hmm, isso é promissor, mas não está funcionando para mim. Quando eu faço import inspect, mypackagee então inspect.getmembers(my_package, inspect.ismodule)recebo uma lista vazia, embora eu certamente tenha vários módulos nela.
Amelio Vazquez-Reina
1
Na verdade, isso só parece funcionar se eu import my_package.fooe não apenas import mypackage, caso em que, então, ele retorna foo. Mas isso derrota o propósito
Amelio Vazquez-Reina
3
@ user815423426 Você está absolutamente certo ;-) Parece que eu estava esquecendo algo.
siebz0r
3

Esta é uma versão recursiva que funciona com o python 3.6 e superior:

import importlib.util
from pathlib import Path
import os
MODULE_EXTENSIONS = '.py'

def package_contents(package_name):
    spec = importlib.util.find_spec(package_name)
    if spec is None:
        return set()

    pathname = Path(spec.origin).parent
    ret = set()
    with os.scandir(pathname) as entries:
        for entry in entries:
            if entry.name.startswith('__'):
                continue
            current = '.'.join((package_name, entry.name.partition('.')[0]))
            if entry.is_file():
                if entry.name.endswith(MODULE_EXTENSIONS):
                    ret.add(current)
            elif entry.is_dir():
                ret.add(current)
                ret |= package_contents(current)


    return ret
Tacaswell
fonte
Qual é a vantagem de usar os.scandircomo um gerenciador de contexto em vez de iterar as entradas de resultado diretamente?
monkut
1
@monkut Veja docs.python.org/3/library/os.html#os.scandir que sugere usá-lo como um gerenciador de contexto para garantir que closeseja chamado quando você terminar de usá- lo para garantir que todos os recursos mantidos sejam liberados.
tacaswell
isso não funciona, em revez disso, ele lista todos os pacotes, mas adiciona re.a todos eles
Tushortz
1

Com base no exemplo de cdleary, aqui está um caminho de listagem de versão recursiva para todos os submódulos:

import imp, os

def iter_submodules(package):
    file, pathname, description = imp.find_module(package)
    for dirpath, _, filenames in os.walk(pathname):
        for  filename in filenames:
            if os.path.splitext(filename)[1] == ".py":
                yield os.path.join(dirpath, filename)
Vajk Hermecz
fonte
0

Isso deve listar os módulos:

help("modules")
Ammon
fonte
0

Se você gostaria de ver uma informação sobre o seu pacote fora do código Python (de um prompt de comando), você pode usar o pydoc para isso.

# get a full list of packages that you have installed on you machine
$ python -m pydoc modules

# get information about a specific package
$ python -m pydoc <your package>

Você terá o mesmo resultado que pydoc, mas dentro do interpretador usando a ajuda

>>> import <my package>
>>> help(<my package>)
Vlad Bezden
fonte
-2
def package_contents(package_name):
  package = __import__(package_name)
  return [module_name for module_name in dir(package) if not module_name.startswith("__")]

fonte
Isso só funciona para módulos, não pacotes. Experimente no loggingpacote do Python para ver o que quero dizer. O registro contém dois módulos: manipuladores e configuração. Seu código retornará uma lista de 66 itens, que não inclui esses dois nomes.
DNS
-3

imprimir dir (módulo)

QueueHammer
fonte
1
Isso lista o conteúdo de um módulo que já foi importado. Estou procurando uma maneira de listar o conteúdo de um pacote que ainda não foi importado, assim como 'de x import *' faz quando nem tudo está especificado.
DNS
from x import * primeiro importa o módulo e depois copia tudo para o módulo atual.
Seb
Percebi que 'from x import *' na verdade não importa submódulos de um pacote, devido a problemas de distinção entre maiúsculas e minúsculas no Windows. Eu apenas incluí isso como um exemplo do que eu queria fazer; Eu editei fora de questão para evitar confusão.
DNS de
Isso lista todos os atributos de um objeto já importado, não apenas uma lista de submódulos. Portanto, não responde à pergunta.
bignose