¿Existe una forma estándar de enumerar los nombres de los módulos de Python en un paquete?

100

¿Existe una forma sencilla de enumerar los nombres de todos los módulos en un paquete, sin usar __all__?

Por ejemplo, dado este paquete:

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

Me pregunto si hay una forma estándar o incorporada de hacer algo como esto:

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

El enfoque manual sería iterar a través de las rutas de búsqueda del módulo para encontrar el directorio del paquete. Luego, uno podría enumerar todos los archivos en ese directorio, filtrar los archivos py / pyc / pyo con nombre exclusivo, eliminar las extensiones y devolver esa lista. Pero esto parece una buena cantidad de trabajo para algo que el mecanismo de importación de módulos ya está haciendo internamente. ¿Esa funcionalidad está expuesta en algún lugar?

DNS
fuente

Respuestas:

23

¿Quizás esto hará lo que estás buscando?

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
fuente
1
Añadiría 'y module! = " Init .py"' ​​al 'if' final, ya que init .py no es realmente parte del paquete. Y .pyo es otra extensión válida. Aparte de eso, usar imp.find_module es una muy buena idea; Creo que esta es la respuesta correcta.
DNS
3
No estoy de acuerdo, puede importar init directamente, entonces, ¿por qué en un caso especial? Seguro que no es lo suficientemente especial como para romper las reglas. ;-)
cdleary
6
Probablemente debería usar en imp.get_suffixes()lugar de su lista escrita a mano.
itsadok
3
Además, tenga en cuenta que esto no funciona en subpaquetes comoxml.sax
itsadok
1
Esta es una forma realmente mala. No se puede saber de forma fiable qué es un módulo a partir de la extensión del nombre de archivo.
wim
188

Usando python2.3 y superior , también puede usar el pkgutilmódulo:

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

EDITAR: Tenga en cuenta que el parámetro no es una lista de módulos, sino una lista de rutas, por lo que es posible que desee hacer algo como esto:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
jp.
fuente
15
Esto es inquietantemente indocumentado, pero parece ser la forma más correcta de hacerlo. Espero que no te importe que agregué la nota.
itsadok
13
pkgutil¿Existe en python2.3 y en realidad ? Además, aunque pkgutil.iter_modules()no funcionará de forma recursiva, también hay una pkgutil.walk_packages(), que se repetirá . Sin embargo, gracias por el puntero a este paquete.
Sandip Bhattacharya
¿ iter_modulesPor qué no funciona para la importación absoluta como a.b.testpkg? Me está dando[]
Hussain
Pasé por alto tu EDITAR :(. Lo siento. Funciona después de seguir el segundo fragmento.
Hussain
1
No puedo confirmar que se pkgutil.walk_packages()repita, me da el mismo resultado que pkgutil.iter_modules(), así que creo que la respuesta está incompleta.
rwst
29
import module
help(module)
Tríptico
fuente
2
Aunque la ayuda enumera el contenido del paquete en la parte inferior del texto de ayuda, la pregunta es más en la línea de cómo hacer esto: f (nombre_paquete) => ["nombre_módulo1", "nombre_módulo2"]. Supongo que podría analizar la cadena devuelta por la ayuda, pero parece más indirecto que enumerar el directorio.
DNS
1
@DNS: help()imprime cosas, no devuelve una cadena.
Junuxx
Estoy de acuerdo en que esta es una forma indirecta, pero me envió por un agujero de conejo para ver cómo help()funciona. De todos modos, la incorporada en el pydocmódulo puede ayudar a escupir la cadena que help()pagina: import pydoc; pydoc.render_doc('mypackage').
sraboy
8

No sé si estoy pasando por alto algo, o si las respuestas están desactualizadas, pero;

Como dijo el usuario 815423426, esto solo funciona para objetos en vivo y los módulos enumerados son solo módulos que se importaron antes.

Enumerar módulos en un paquete parece realmente fácil usando inspeccionar :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
fuente
He puesto importado = import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule) pero la ruta importada es ~ / myproj / __ init .py ym es una lista con (mymod, '~ /myproj/mymod/__init__.py ')
hithwen
1
@hithwen No hagas preguntas en los comentarios, especialmente si no están directamente relacionados. Ser un buen samaritano: Use imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__importa el módulo de nivel superior, consulte la documentación .
siebz0r
Hmm, esto es prometedor pero no me está funcionando. Cuando lo hago import inspect, mypackagey luego inspect.getmembers(my_package, inspect.ismodule)obtengo una lista vacía, aunque ciertamente tengo varios módulos en ella.
Amelio Vazquez-Reina
1
De hecho, esto solo parece funcionar si yo import my_package.fooy no solo import mypackage, en cuyo caso regresa foo. Pero esto anula el propósito
Amelio Vazquez-Reina
3
@ user815423426 Tienes toda la razón ;-) Parece que estaba pasando por alto algo.
siebz0r
3

Esta es una versión recursiva que funciona con python 3.6 y 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
fuente
¿Cuál es la ventaja de usarlo os.scandircomo administrador de contexto en lugar de iterar sobre las entradas de resultados directamente?
Monkut
1
@monkut Consulte docs.python.org/3/library/os.html#os.scandir, que sugiere usarlo como administrador de contexto para asegurarse de que closese llame cuando haya terminado con él para asegurarse de que se liberen los recursos retenidos.
tacaswell
esto no funciona porque, en recambio, enumera todos los paquetes, pero los agrega re.a todos
Tushortz
1

Basado en el ejemplo de cdleary, aquí hay una ruta de lista de versiones recursivas para todos los 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
fuente
0

Esto debería enumerar los módulos:

help("modules")
Amón
fuente
0

Si desea ver una información sobre su paquete fuera del código de Python (desde un símbolo del sistema), puede usar Pydoc para ello.

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

Tendrá el mismo resultado que pydoc pero dentro del intérprete usando ayuda

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

fuente
Eso solo funciona para módulos, no para paquetes. Pruébelo en el loggingpaquete de Python para ver a qué me refiero. El registro contiene dos módulos: controladores y configuración. Su código devolverá una lista de 66 elementos, que no incluye esos dos nombres.
DNS
-3

imprimir dir (módulo)

QueueHammer
fuente
1
Eso enumera el contenido de un módulo que ya se ha importado. Estoy buscando una manera de mostrar el contenido de un paquete que aún no ha sido importado, al igual que 'desde x importación *' hace cuando todo no se especifica.
DNS
from x import * primero importa el módulo y luego copia todo al módulo actual.
Seb
Me di cuenta de que 'from x import *' no importa submódulos de un paquete, debido a problemas de distinción entre mayúsculas y minúsculas en Windows. Solo incluí eso como un ejemplo de lo que quería hacer; Lo eliminé de la cuestión para evitar confusiones.
DNS
Eso enumera todos los atributos de un objeto ya importado, no solo una lista de submódulos. Entonces no responde la pregunta.
bignose