Esiste un modo standard per elencare i nomi dei moduli Python in un pacchetto?


100

Esiste un modo semplice per elencare i nomi di tutti i moduli in un pacchetto, senza utilizzarli __all__?

Ad esempio, dato questo pacchetto:

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

Mi chiedo se esiste un modo standard o integrato per fare qualcosa del genere:

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

L'approccio manuale consiste nell'iterare i percorsi di ricerca del modulo per trovare la directory del pacchetto. Si potrebbe quindi elencare tutti i file in quella directory, filtrare i file py / pyc / pyo dal nome univoco, rimuovere le estensioni e restituire quell'elenco. Ma questo sembra una discreta quantità di lavoro per qualcosa che il meccanismo di importazione dei moduli sta già facendo internamente. Quella funzionalità è esposta da qualche parte?

Risposte:


23

Forse questo farà quello che stai cercando?

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

1
Vorrei aggiungere 'e module! = " Init .py"' ​​al finale 'if', poiché init .py non fa realmente parte del pacchetto. E .pyo è un'altra estensione valida. A parte questo, usare imp.find_module è davvero una buona idea; Penso che questa sia la risposta giusta.
DNS

3
Non sono d'accordo - puoi importare init direttamente, quindi perché è un caso speciale? Di certo non è abbastanza speciale da infrangere le regole. ;-)
cdleary

6
Probabilmente dovresti usare al imp.get_suffixes()posto del tuo elenco scritto a mano.
itsadok

3
Inoltre, xml.sax
tieni

1
Questo è davvero un brutto modo. Non puoi dire in modo affidabile cos'è un modulo dall'estensione del nome del file.
wim

188

Usando python2.3 e versioni successive , potresti anche usare il pkgutilmodulo:

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

EDIT: nota che il parametro non è un elenco di moduli, ma un elenco di percorsi, quindi potresti voler fare qualcosa del genere:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]

15
Questo è incredibilmente non documentato, ma sembra il modo più corretto per farlo. Spero non ti dispiaccia se ho aggiunto la nota.
itsadok

13
pkgutilè presente in python2.3 e versioni successive . Inoltre, mentre pkgutil.iter_modules()non funziona in modo ricorsivo, v'è una pkgutil.walk_packages()pure, che verrà recurse. Grazie per il puntatore a questo pacchetto però.
Sandip Bhattacharya

Perché iter_modulesnon funziona per l'importazione assoluta come a.b.testpkg? Mi sta dando[]
Hussain

Ho trascurato la tua MODIFICA :(. Scusa. Funziona dopo aver seguito il secondo frammento.
Hussain

1
Non posso confermare che pkgutil.walk_packages()ricorre, mi dà lo stesso output di pkgutil.iter_modules(), quindi penso che la risposta sia incompleta.
rwst

29
import module
help(module)

2
Anche se la guida elenca il contenuto del pacchetto in fondo al testo della guida, la domanda è più simile a come farlo: f (nome_pacchetto) => ["nome_modulo1", "nome_modulo2"]. Suppongo di poter analizzare la stringa restituita da help, ma sembra più indiretto che elencare la directory.
DNS

1
@DNS: help()stampa cose, non restituisce una stringa.
Junuxx

Sono d'accordo che questo sia un modo indiretto, ma mi ha mandato in una tana del coniglio per vedere come help()funziona. In ogni caso, il built-in pydocmodulo può aiutare a sputare fuori la stringa che help()impagina: import pydoc; pydoc.render_doc('mypackage').
sraboy

8

Non so se sto trascurando qualcosa, o se le risposte sono solo obsolete ma;

Come affermato da user815423426, funziona solo per oggetti live ei moduli elencati sono solo moduli importati in precedenza.

Elencare i moduli in un pacchetto sembra davvero facile usando inspect :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']

Ho inserito import = import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule) ma il percorso importd è ~ / myproj / __ init .py e m è un elenco con (mymod, '~ /myproj/mymod/__init__.py ')
hithwen

1
@hithwen Non fare domande nei commenti, soprattutto se non sono direttamente correlati. Essere un buon samaritano: usa imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__importa il modulo di primo livello, vedere la documentazione .
siebz0r

Hmm, questo è promettente ma non funziona per me. Quando lo faccio import inspect, mypackagee poi inspect.getmembers(my_package, inspect.ismodule)ottengo una lista vuota, anche se certamente ho vari moduli in essa.
Amelio Vazquez-Reina

1
È un dato di fatto, questo sembra funzionare solo se io import my_package.fooe non solo import mypackage, nel qual caso poi ritorna foo. Ma questo sconfigge lo scopo
Amelio Vazquez-Reina

3
@ user815423426 Hai perfettamente ragione ;-) Sembra che stavo trascurando qualcosa.
siebz0r

3

Questa è una versione ricorsiva che funziona con python 3.6 e versioni successive:

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

Qual è il vantaggio di utilizzare os.scandircome gestore di contesto invece di iterare direttamente sulle voci dei risultati?
monkut

1
@monkut Vedi docs.python.org/3/library/os.html#os.scandir che suggerisce di usarlo come gestore di contesto per assicurarti che closevenga chiamato quando hai finito per assicurarti che tutte le risorse detenute vengano rilasciate.
tacaswell

questo non funziona reperché elenca tutti i pacchetti ma li aggiunge re.a tutti
Tushortz

1

Sulla base dell'esempio di cdleary, ecco un percorso di elenco delle versioni ricorsivo per tutti i sottomoduli:

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)

0

Questo dovrebbe elencare i moduli:

help("modules")

0

Se desideri visualizzare un'informazione sul tuo pacchetto al di fuori del codice python (da un prompt dei comandi) puoi usare pydoc per questo.

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

Avrai lo stesso risultato di pydoc ma all'interno dell'interprete usando l'aiuto

>>> import <my package>
>>> help(<my package>)

-2
def package_contents(package_name):
  package = __import__(package_name)
  return [module_name for module_name in dir(package) if not module_name.startswith("__")]

Funziona solo per i moduli, non per i pacchetti. Provalo sul loggingpacchetto di Python per vedere cosa intendo. La registrazione contiene due moduli: gestori e config. Il codice restituirà un elenco di 66 elementi, che non include questi due nomi.
DNS

-3

print dir (modulo)


1
Questo elenca i contenuti di un modulo che è già stato importato. Sto cercando un modo per elencare il contenuto di un pacchetto che non è stato ancora importato, proprio come fa 'from x import *' quando non è tutto specificato.
DNS

from x import * importa prima il modulo e poi copia tutto nel modulo corrente.
Seb

Mi sono reso conto che "from x import *" in realtà non importa i sottomoduli di un pacchetto, a causa di problemi di distinzione tra maiuscole e minuscole su Windows. L'ho incluso solo come esempio di ciò che volevo fare; L'ho modificato fuori questione per evitare confusione.
DNS

Questo elenca tutti gli attributi di un oggetto già importato, non solo un elenco di sottomoduli. Quindi non risponde alla domanda.
bignose
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.