¿Enumerar todos los módulos que forman parte de un paquete de Python?

107

¿Existe una forma sencilla de encontrar todos los módulos que forman parte de un paquete de Python? Encontré esta vieja discusión , que no es realmente concluyente, pero me encantaría tener una respuesta definitiva antes de implementar mi propia solución basada en os.listdir ().

static_rtti
fuente
6
@ S.Lott: Hay soluciones más generales disponibles, los paquetes de Python no siempre están en directorios en el sistema de archivos, pero también pueden estar dentro de cremalleras.
u0b34a0f6ae
4
¿Por qué reinventar la rueda? Si python adquiere hipermódulos en Python 4, pkgutil y se actualiza con eso, mi código seguirá funcionando. Me gusta usar abstracciones que están disponibles. Utilice el método obvio proporcionado, está probado y se sabe que funciona. Reimplementando eso ... ahora tienes que encontrar y trabajar en cada caso de esquina tú mismo.
u0b34a0f6ae
1
@ S.Lott: Entonces, cada vez que se inicia la aplicación, ¿descomprimirá su propio huevo si se instala dentro de uno solo para verificar esto? Envíe un parche contra mi proyecto para reinventar la rueda en esta función: git.gnome.org/cgit/kupfer/tree/kupfer/plugins.py#n17 . Tenga en cuenta tanto los huevos como los directorios normales, no supere las 20 líneas.
u0b34a0f6ae
1
@ S.Lott: Por qué no entiendes que es relevante es algo que no puedes entender. Descubrir esto mediante programación se trata de que la aplicación se interese en el contenido de un paquete, no en el usuario.
u0b34a0f6ae
3
¡Por supuesto que me refiero a programáticamente! De lo contrario, no habría mencionado "implementar mi propia solución con os.listdir ()"
static_rtti

Respuestas:

145

Sí, desea algo basado en pkgutilo similar; de esta manera, puede tratar todos los paquetes por igual, independientemente de si están en huevos o cremalleras o algo así (donde os.listdir no ayudará).

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)

¿Cómo importarlos también? Puedes usarlo __import__como de costumbre:

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
fuente
9
¿Qué importerdevuelve esto pkgutil.iter_modules? ¿Puedo usarlo para importar un módulo en lugar de usar este aparentemente "truco" __import__(modname, fromlist="dummy")?
MestreLion
29
Pude usar el importador de esta manera: m = importer.find_module(modname).load_module(modname)y luego mestá el módulo, por ejemplo:m.myfunc()
chrisleague
@chrisleague Estaba usando nuestro método con Python 2.7, pero ahora necesito seguir adelante con Python 3.4, así que sabes que en Python 3 pkutil.iter_modules produce (module_finder, name, ispkg) en lugar de (module_loader, name, ispkg). ¿Qué puedo hacer para que funcione como el anterior?
crax
Su primer ejemplo produce el siguiente error: "AttributeError: el objeto 'módulo' no tiene atributo ' _path_ '" ¿Tiene esto algo que ver con la versión de Python? (Yo uso Python 2.7)
Apostolos
@Apostolos, está utilizando solo un guión bajo a cada lado de la ruta (es decir _path_). Debe haber dos a cada lado, para un total de cuatro (es decir __path__).
therealmitchconnors
46

La herramienta adecuada para este trabajo es pkgutil.walk_packages.

Para enumerar todos los módulos de su sistema:

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

Tenga en cuenta que walk_packages importa todos los subpaquetes, pero no los submódulos.

Si desea enumerar todos los submódulos de un determinado paquete, puede usar algo como esto:

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 solo enumera los módulos que tienen un nivel de profundidad. walk_packages obtiene todos los submódulos. En el caso de scipy, por ejemplo, walk_packages devuelve

scipy.stats.stats

mientras que iter_modules solo devuelve

scipy.stats

La documentación sobre pkgutil ( http://docs.python.org/library/pkgutil.html ) no enumera todas las funciones interesantes definidas en /usr/lib/python2.6/pkgutil.py.

Quizás esto signifique que las funciones no son parte de la interfaz "pública" y están sujetas a cambios.

Sin embargo, al menos a partir de Python 2.6 (¿y quizás versiones anteriores?) Pkgutil viene con un método walk_packages que recorre recursivamente todos los módulos disponibles.

unutbu
fuente
5
walk_packagesestá ahora en la documentación: docs.python.org/library/pkgutil.html#pkgutil.walk_packages
Caracol mecánico
1
Su segundo ejemplo produce el siguiente error: "AttributeError: el objeto 'módulo' no tiene atributo ' _path_ '" - No lo probé con 'scipy' sino con algunos otros paquetes. ¿Tiene esto algo que ver con la versión de Python? (Yo uso Python 2.7)
Apostolos
1
@Apostolos: Debe haber dos guiones bajos ( _) antes y después path, es decir, use enpackage.__path__ lugar de package._path_. Puede ser más fácil intentar cortar y pegar el código en lugar de volver a escribirlo.
unutbu
¡Había dos cuando escribí el comentario! :) Pero han sido eliminados por el sistema. Culpa mía; Debería haber puesto tres subnúcleos. Pero entonces, esto estaría bien si quisiera usar cursiva, ¡lo cual no hice! ... Es una situación de pérdida-pérdida. :) De todos modos, cuando ejecuté el código usé dos de ellos, por supuesto. (Copié y pegué el código.)
Apostolos
@Apostolos: asegúrese de que la variable packageapunte a un paquete, no a un módulo. Los módulos son archivos, mientras que los paquetes son directorios. Todos los paquetes tienen el __path__atributo (... a menos que alguien ha eliminado el atributo por alguna razón.)
unutbu
2

Esto funciona para mi:

import types

for key, obj in nltk.__dict__.iteritems():
    if type(obj) is types.ModuleType: 
        print key
DarinP
fuente
1
Esto falla de dos maneras 1. Los paquetes no siempre importan explícitamente sus submódulos en el espacio de nombres de nivel superior 2. Los paquetes pueden importar otros módulos de terceros en su espacio de nombres de nivel superior
wim
0

Estaba buscando una forma de recargar todos los submódulos que estoy editando en vivo en mi paquete. Es una combinación de las respuestas / comentarios anteriores, así que decidí publicarlo aquí como una respuesta en lugar de un comentario.

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))
usuario1767754
fuente
-4

Aquí hay una forma, fuera de mi cabeza:

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

Sin duda, podría limpiarse y mejorarse.

EDITAR: Aquí hay una versión un poco mejor:

>>> [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: Esto también encontrará módulos que pueden no estar necesariamente ubicados en un subdirectorio del paquete, si están incluidos en su __init__.pyarchivo, por lo que depende de lo que usted quiera decir con "parte de" un paquete.

Steve Losh
fuente
lo siento, eso no sirve de nada. Aparte de los falsos positivos, solo encontrará submódulos de paquetes ya importados.
u0b34a0f6ae