Existe-t-il un moyen standard de lister les noms des modules Python dans un package?

100

Existe-t-il un moyen simple de lister les noms de tous les modules d'un package, sans utiliser __all__?

Par exemple, étant donné ce package:

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

Je me demande s'il existe un moyen standard ou intégré de faire quelque chose comme ceci:

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

L'approche manuelle consisterait à parcourir les chemins de recherche du module afin de trouver le répertoire du paquet. On pourrait alors lister tous les fichiers dans ce répertoire, filtrer les fichiers py / pyc / pyo de nom unique, supprimer les extensions et renvoyer cette liste. Mais cela semble être un travail considérable pour quelque chose que le mécanisme d'importation de modules fait déjà en interne. Cette fonctionnalité est-elle exposée quelque part?

DNS
la source

Réponses:

23

Peut-être que cela fera ce que vous recherchez?

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
la source
1
J'ajouterais 'et module! = " Init .py"' ​​au 'if' final, puisque init .py ne fait pas vraiment partie du paquet. Et .pyo est une autre extension valide. En dehors de cela, utiliser imp.find_module est une très bonne idée; Je pense que c'est la bonne réponse.
DNS
3
Je ne suis pas d'accord - vous pouvez importer directement init , alors pourquoi un cas particulier? Ce n'est certainement pas assez spécial pour enfreindre les règles. ;-)
cdleary
6
Vous devriez probablement utiliser à la imp.get_suffixes()place de votre liste manuscrite.
itsadok
3
Notez également que cela ne fonctionne pas sur des sous-packages commexml.sax
itsadok
1
C'est une très mauvaise façon. Vous ne pouvez pas dire de manière fiable ce qu'est un module à partir de l'extension de nom de fichier.
wim
189

En utilisant python2.3 et supérieur , vous pouvez également utiliser le pkgutilmodule:

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

EDIT: Notez que le paramètre n'est pas une liste de modules, mais une liste de chemins, vous voudrez peut-être faire quelque chose comme ceci:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
jp.
la source
15
Ceci est étonnamment non documenté, mais semble être la manière la plus correcte de le faire. J'espère que cela ne vous dérange pas, j'ai ajouté la note.
itsadok
13
pkgutilexiste en python2.3 et plus en fait . De plus, même si pkgutil.iter_modules()cela ne fonctionnera pas de manière récursive, il existe également un système pkgutil.walk_packages()qui se répètera. Merci pour le pointeur vers ce package.
Sandip Bhattacharya
Pourquoi iter_modulesne fonctionne pas pour l'importation absolue comme a.b.testpkg? It is give me[]
Hussain
J'ai oublié votre EDIT: (. Désolé. Cela fonctionne après avoir suivi le deuxième extrait.
Hussain
1
Je ne peux pas confirmer que cela se pkgutil.walk_packages()répète, cela me donne le même résultat que pkgutil.iter_modules(), donc je pense que la réponse est incomplète.
rwst
29
import module
help(module)
Triptyque
la source
2
Bien que l'aide répertorie le contenu du paquet au bas du texte d'aide, la question est plutôt de savoir comment faire ceci: f (package_name) => ["module1_name", "module2_name"]. Je suppose que je pourrais analyser la chaîne renvoyée par l'aide, mais cela semble plus détourné que de lister le répertoire.
DNS
1
@DNS: help()imprime des trucs, il ne renvoie pas de chaîne.
Junuxx
Je suis d'accord que c'est un chemin détourné mais cela m'a envoyé dans un terrier de lapin pour voir comment help()fonctionne. Quoi qu'il en soit, le haut- pydocmodule peut cracher aider la chaîne help()pagine: import pydoc; pydoc.render_doc('mypackage').
sraboy
8

Je ne sais pas si j'oublie quelque chose, ou si les réponses sont simplement dépassées mais;

Comme indiqué par user815423426, cela ne fonctionne que pour les objets en direct et les modules répertoriés ne sont que des modules qui ont été importés auparavant.

Lister des modules dans un package semble très simple en utilisant inspect :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
la source
J'ai mis importé = import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule) mais le chemin importd est ~ / myproj / __ init .py et m est une liste avec (mymod, '~ /myproj/mymod/__init__.py ')
hithwen
1
@hithwen Ne posez pas de questions dans les commentaires, surtout s'ils ne sont pas directement liés. Être un bon samaritain: utilisez imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__importe le module de niveau supérieur, voir la documentation .
siebz0r
Hmm, c'est prometteur mais ça ne marche pas pour moi. Quand je le fais import inspect, mypackageet puis inspect.getmembers(my_package, inspect.ismodule)j'obtiens une liste vide, même si j'ai certainement plusieurs modules dedans.
Amelio Vazquez-Reina
1
En fait, cela ne semble fonctionner que si je import my_package.fooet pas seulement import mypackage, auquel cas il revient ensuite foo. Mais cela va à l'encontre du but
Amelio Vazquez-Reina
3
@ user815423426 Vous avez absolument raison ;-) On dirait que j'ai oublié quelque chose.
siebz0r
3

Il s'agit d'une version récursive qui fonctionne avec python 3.6 et supérieur:

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
la source
Quel est l'avantage d'utiliser os.scandircomme gestionnaire de contexte au lieu d'itérer directement les entrées de résultat?
monkut
1
@monkut Voir docs.python.org/3/library/os.html#os.scandir qui suggère de l'utiliser comme gestionnaire de contexte pour s'assurer qu'il closeest appelé lorsque vous en avez terminé pour vous assurer que toutes les ressources détenues sont libérées.
tacaswell le
cela ne fonctionne pas car à la replace, il répertorie tous les paquets mais les ajoute re.à tous
Tushortz
1

Basé sur l'exemple de cdleary, voici un chemin d'accès de liste des versions récursives pour tous les sous-modules:

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
la source
0

Cela devrait lister les modules:

help("modules")
Ammon
la source
0

Si vous souhaitez afficher des informations sur votre package en dehors du code python (à partir d'une invite de commande), vous pouvez utiliser pydoc pour cela.

# 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>

Vous aurez le même résultat que pydoc mais à l'intérieur de l'interpréteur en utilisant l'aide

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

la source
Cela ne fonctionne que pour les modules, pas pour les packages. Essayez-le sur le loggingpackage de Python pour voir ce que je veux dire. La journalisation contient deux modules: handlers et config. Votre code renverra une liste de 66 éléments, qui n'inclut pas ces deux noms.
DNS
-3

rép d'impression (module)

QueueHammer
la source
1
Cela répertorie le contenu d'un module qui a déjà été importé. Je cherche un moyen de lister le contenu d'un package qui n'a pas encore été importé, tout comme «from x import *» le fait quand tout n'est pas spécifié.
DNS
from x import * importe d'abord le module, puis copie tout dans le module actuel.
Seb
J'ai réalisé que 'from x import *' n'importait en fait pas les sous-modules d'un package, en raison de problèmes de sensibilité à la casse sous Windows. Je n'ai inclus cela qu'à titre d'exemple de ce que je voulais faire; Je l'ai édité hors de question pour éviter toute confusion.
DNS
Cela répertorie tous les attributs d'un objet déjà importé, pas seulement une liste de sous-modules. Cela ne répond donc pas à la question.
bignose