Listez tous les modules qui font partie d'un package python?

107

Existe-t-il un moyen simple de trouver tous les modules qui font partie d'un package python? J'ai trouvé cette vieille discussion , qui n'est pas vraiment concluante, mais j'aimerais avoir une réponse définitive avant de déployer ma propre solution basée sur os.listdir ().

static_rtti
la source
6
@ S.Lott: Il y a des solutions plus générales disponibles, les paquets python ne sont pas toujours dans les répertoires du système de fichiers, mais peuvent aussi être dans des zips.
u0b34a0f6ae
4
pourquoi réinventer la roue? Si python acquiert des hypermodules dans Python 4, pkgutil et mis à jour avec cela, mon code fonctionnera toujours. J'aime utiliser les abstractions disponibles. Utilisez la méthode évidente fournie, elle est testée et connue pour fonctionner. Réimplémenter cela .. maintenant, vous devez trouver et travailler vous-même dans chaque cas de coin.
u0b34a0f6ae
1
@ S.Lott: Donc à chaque fois que l'application démarre, elle décompressera son propre œuf si elle est installée à l'intérieur d'un seul pour vérifier cela? Veuillez soumettre un correctif contre mon projet pour réinventer la roue dans cette fonction: git.gnome.org/cgit/kupfer/tree/kupfer/plugins.py#n17 . Veuillez considérer les œufs et les répertoires normaux, ne dépassez pas 20 lignes.
u0b34a0f6ae
1
@ S.Lott: Pourquoi vous ne comprenez pas que c'est pertinent est quelque chose que vous ne pouvez pas comprendre. Découvrir cela par programme signifie que l' application s'intéresse au contenu d'un package, pas à l'utilisateur.
u0b34a0f6ae
3
Bien sûr, je veux dire par programme! Sinon, je n'aurais pas mentionné "déployer ma propre solution avec os.listdir ()"
static_rtti

Réponses:

145

Oui, vous voulez quelque chose basé sur pkgutilou similaire - de cette façon, vous pouvez traiter tous les paquets de la même manière, qu'ils soient dans des œufs ou des zips ou quelque chose de similaire (où os.listdir n'aidera pas).

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)

Comment les importer aussi? Vous pouvez simplement utiliser __import__comme d'habitude:

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
la source
9
par quoi est-ce importerretourné pkgutil.iter_modules? Puis-je l'utiliser pour importer un module au lieu d'utiliser ce "hackish" apparent __import__(modname, fromlist="dummy")?
MestreLion
29
J'ai pu utiliser l'importateur comme ceci: m = importer.find_module(modname).load_module(modname)et puis mest le module, donc par exemple:m.myfunc()
chrisleague
@chrisleague J'utilisais la méthode ur avec python 2.7, mais maintenant je dois passer à python 3.4, vous savez donc qu'en python 3 pkutil.iter_modules donne (module_finder, name, ispkg) au lieu de (module_loader, name, ispkg). Que puis-je faire pour que cela fonctionne comme le précédent?
crax
Votre premier exemple produit l'erreur suivante: "AttributeError: l'objet 'module' n'a pas d'attribut ' _path_ '" Cela a-t-il quelque chose à voir avec la version Python? (J'utilise Python 2.7)
Apostolos
@Apostolos, vous n'utilisez qu'un seul trait de soulignement de chaque côté du chemin (ie _path_). Il devrait y en avoir deux de chaque côté, pour un total de quatre (c'est-à-dire __path__).
therealmitchconnors
46

Le bon outil pour ce travail est pkgutil.walk_packages.

Pour lister tous les modules de votre système:

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

Sachez que walk_packages importe tous les sous-packages, mais pas les sous-modules.

Si vous souhaitez lister tous les sous-modules d'un certain package, vous pouvez utiliser quelque chose comme ceci:

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 ne répertorie que les modules qui ont une profondeur d'un niveau. walk_packages récupère tous les sous-modules. Dans le cas de scipy, par exemple, walk_packages renvoie

scipy.stats.stats

tandis que iter_modules ne renvoie que

scipy.stats

La documentation sur pkgutil ( http://docs.python.org/library/pkgutil.html ) ne répertorie pas toutes les fonctions intéressantes définies dans /usr/lib/python2.6/pkgutil.py.

Cela signifie peut-être que les fonctions ne font pas partie de l'interface "publique" et sont susceptibles de changer.

Cependant, au moins à partir de Python 2.6 (et peut-être des versions antérieures?) Pkgutil est livré avec une méthode walk_packages qui parcourt de manière récursive tous les modules disponibles.

unutbu
la source
5
walk_packagesest maintenant dans la documentation: docs.python.org/library/pkgutil.html#pkgutil.walk_packages
mécanique du
1
Votre deuxième exemple produit l'erreur suivante: "AttributeError: l'objet 'module' n'a pas d'attribut ' _path_ '" - Je ne l'ai pas testé avec 'scipy' mais avec quelques autres packages. Cela a-t-il quelque chose à voir avec la version Python? (J'utilise Python 2.7)
Apostolos
1
@Apostolos: Il devrait y avoir deux traits de soulignement ( _) avant et après path- c'est-à-dire, utilisezpackage.__path__ plutôt que package._path_. Il peut être plus facile d'essayer de couper et coller le code plutôt que de le retaper.
unutbu
Il y en avait deux, quand j'ai écrit le commentaire! :) Mais ils ont été dépouillés par le système. Ma faute; J'aurais dû mettre trois points. Mais alors, ce serait OK si je voulais utiliser des italiques, ce que je n'ai pas fait! ... C'est une situation de perte-perte. :) Quoi qu'il en soit, quand j'ai exécuté le code, j'en ai utilisé deux, bien sûr. (J'ai copié-collé le code.)
Apostolos
@Apostolos: assurez-vous que la variable packagepointe vers un package, pas un module. Les modules sont des fichiers tandis que les packages sont des répertoires. Tous les packages ont l' __path__attribut (... sauf si quelqu'un a supprimé l'attribut pour une raison quelconque.)
unutbu
2

Cela fonctionne pour moi:

import types

for key, obj in nltk.__dict__.iteritems():
    if type(obj) is types.ModuleType: 
        print key
DarinP
la source
1
Cela échoue de deux manières 1. les packages n'importent pas toujours explicitement leurs sous-modules dans l'espace de noms de niveau supérieur 2. les packages peuvent importer d'autres modules tiers dans leur espace de noms de niveau supérieur
wim
0

Je cherchais un moyen de recharger tous les sous-modules que j'édite en direct dans mon package. C'est une combinaison des réponses / commentaires ci-dessus, j'ai donc décidé de l'afficher ici comme une réponse plutôt qu'un commentaire.

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

Voici une façon, du haut de ma tête:

>>> 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)>]

Il pourrait certainement être nettoyé et amélioré.

EDIT: Voici une version légèrement plus agréable:

>>> [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']

REMARQUE: Cela trouvera également des modules qui ne se trouvent pas nécessairement dans un sous-répertoire du package, s'ils sont extraits dans son __init__.pyfichier, donc cela dépend de ce que vous entendez par «partie» d'un package.

Steve Losh
la source
désolé, cela ne sert à rien. Mis à part les faux positifs, il ne trouvera que les sous-modules de packages déjà importés.
u0b34a0f6ae