Apakah ada cara standar untuk membuat daftar nama modul Python dalam sebuah paket?

101

Apakah ada cara mudah untuk membuat daftar nama semua modul dalam sebuah paket, tanpa menggunakan __all__?

Misalnya, diberikan paket ini:

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

Saya ingin tahu apakah ada cara standar atau bawaan untuk melakukan sesuatu seperti ini:

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

Pendekatan manual akan melakukan iterasi melalui jalur pencarian modul untuk menemukan direktori paket. Seseorang kemudian dapat membuat daftar semua file dalam direktori itu, menyaring file py / pyc / pyo yang dinamai unik, menghapus ekstensi, dan mengembalikan daftar itu. Tapi ini sepertinya cukup banyak pekerjaan untuk sesuatu yang mekanisme impor modul sudah lakukan secara internal. Apakah fungsi itu terpapar di mana saja?

DNS
sumber

Jawaban:

23

Mungkin ini akan melakukan apa yang Anda cari?

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
sumber
1
Saya akan menambahkan 'and module! = " Init .py"' ​​ke akhir 'if', karena init .py sebenarnya bukan bagian dari paket. Dan .pyo adalah ekstensi valid lainnya. Selain itu, menggunakan imp.find_module adalah ide yang sangat bagus; Saya rasa ini adalah jawaban yang benar.
DNS
3
Saya tidak setuju - Anda dapat mengimpor init secara langsung, jadi mengapa perlu kasus khusus? Jelas tidak cukup istimewa untuk melanggar aturan. ;-)
cdleary
6
Anda mungkin harus menggunakan imp.get_suffixes()daripada daftar tulisan tangan Anda.
itsadok
3
Juga, perhatikan bahwa ini tidak berfungsi pada sub-paket sepertixml.sax
itsadok
1
Ini cara yang sangat buruk. Anda tidak dapat mengetahui dengan pasti apa itu modul dari ekstensi nama file.
wim
190

Menggunakan python2.3 dan yang lebih baru , Anda juga bisa menggunakan pkgutilmodul:

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

EDIT: Perhatikan bahwa parameternya bukanlah daftar modul, tetapi daftar jalur, jadi Anda mungkin ingin melakukan sesuatu seperti ini:

>>> import os.path, pkgutil
>>> import testpkg
>>> pkgpath = os.path.dirname(testpkg.__file__)
>>> print [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
jp.
sumber
15
Ini sangat tidak terdokumentasi, tetapi sepertinya cara yang paling benar untuk melakukan ini. Harap Anda tidak keberatan saya menambahkan catatan.
itsadok
13
pkgutilapakah ada di python2.3 dan lebih tinggi sebenarnya . Juga, meski pkgutil.iter_modules()tidak akan bekerja secara rekursif, ada pkgutil.walk_packages()juga, yang akan berulang. Terima kasih atas penunjuk ke paket ini.
Sandip Bhattacharya
Mengapa iter_modulestidak berfungsi untuk impor absolut seperti a.b.testpkg? Itu memberi saya[]
Hussain
Saya mengabaikan EDIT Anda :(. Maaf. Ini berfungsi setelah saya mengikuti cuplikan kedua.
Hussain
1
Saya tidak dapat memastikan bahwa itu pkgutil.walk_packages()berulang, itu memberi saya hasil yang sama seperti pkgutil.iter_modules(), jadi saya pikir jawabannya tidak lengkap.
pertama
29
import module
help(module)
Triptych
sumber
2
Meskipun bantuan mencantumkan konten paket di bagian bawah teks bantuan, pertanyaannya lebih pada bagaimana melakukan ini: f (package_name) => ["module1_name", "module2_name"]. Saya kira saya bisa mengurai string yang dikembalikan dengan bantuan, tetapi itu tampaknya lebih bundar daripada daftar direktori.
DNS
1
@DNS: help()mencetak barang, tidak mengembalikan string.
Junuxx
Saya setuju ini adalah jalan memutar tetapi itu mengirim saya ke lubang kelinci untuk melihat cara help()kerjanya. Pokoknya, built-in pydocmodul dapat membantu meludah keluar string yang help()nomor laman: import pydoc; pydoc.render_doc('mypackage').
sraboy
8

Tidak tahu apakah saya melewatkan sesuatu, atau jika jawabannya hanya ketinggalan zaman tetapi;

Seperti yang dinyatakan oleh user815423426 ini hanya berfungsi untuk objek hidup dan modul yang terdaftar hanya modul yang diimpor sebelumnya.

Daftar modul dalam sebuah paket tampaknya sangat mudah menggunakan inspect :

>>> import inspect, testpkg
>>> inspect.getmembers(testpkg, inspect.ismodule)
['modulea', 'moduleb']
siebz0r
sumber
Saya telah menempatkan import = import __ ('myproj.mymod.mysubmod') m = inspect.getmembers (i, inspect.ismodule) tetapi jalur importd adalah ~ / myproj / __ init .py dan m adalah daftar dengan (mymod, '~ /myproj/mymod/__init__.py ')
hithwen
1
@hithwen Jangan ajukan pertanyaan di komentar, terutama jika tidak terkait langsung. Menjadi Orang Samaria yang Baik: Gunakan imported = import importlib; importlib.import_module('myproj.mymod.mysubmod'). __import__mengimpor modul level atas, lihat dokumentasi .
siebz0r
Hmm, ini menjanjikan tetapi tidak berhasil untuk saya. Ketika saya melakukannya import inspect, mypackagedan kemudian inspect.getmembers(my_package, inspect.ismodule)saya mendapatkan daftar kosong, meskipun saya pasti memiliki berbagai modul di dalamnya.
Amelio Vazquez-Reina
1
Faktanya, ini hanya bekerja jika saya import my_package.foodan tidak hanya import mypackage, dalam hal ini kemudian kembali foo. Tapi ini mengalahkan tujuan
Amelio Vazquez-Reina
3
@ user815423426 Anda benar sekali ;-) Sepertinya saya melewatkan sesuatu.
siebz0r
3

Ini adalah versi rekursif yang berfungsi dengan python 3.6 ke atas:

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.dll
sumber
Apa keuntungan menggunakan os.scandirsebagai manajer konteks daripada mengulang entri hasil secara langsung?
monkut
1
@monkut Lihat docs.python.org/3/library/os.html#os.scandir yang menyarankan untuk menggunakannya sebagai pengelola konteks untuk memastikan bahwa closedipanggil saat Anda selesai menggunakannya untuk memastikan bahwa semua sumber daya yang dipegang dilepaskan.
tacaswell
ini tidak berfungsi karena reia mencantumkan setiap paket tetapi menambahkan re.ke semuanya
Tushortz
1

Berdasarkan contoh cdleary, berikut adalah jalur listingan versi rekursif untuk semua submodul:

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
sumber
0

Ini harus mencantumkan modul:

help("modules")
Amon
sumber
0

Jika Anda ingin melihat informasi tentang paket Anda di luar kode python (dari prompt perintah), Anda dapat menggunakan pydoc untuk itu.

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

Anda akan mendapatkan hasil yang sama dengan pydoc tetapi di dalam interpreter menggunakan bantuan

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

sumber
Itu hanya berfungsi untuk modul, bukan paket. Cobalah di loggingpaket Python untuk melihat apa yang saya maksud. Logging berisi dua modul: penangan dan konfigurasi. Kode Anda akan mengembalikan daftar 66 item, yang tidak menyertakan kedua nama itu.
DNS
-3

cetak dir (modul)

QueueHammer
sumber
1
Itu daftar isi modul yang telah diimpor. Saya mencari cara untuk membuat daftar isi dari sebuah paket yang belum diimpor, seperti yang dilakukan 'from x import *' ketika semua tidak ditentukan.
DNS
from x import * pertama mengimpor modul dan kemudian menyalin semuanya ke modul saat ini.
Seb
Saya menyadari bahwa 'dari x import *' sebenarnya tidak mengimpor sub-modul dari sebuah paket, karena masalah sensitivitas huruf pada Windows. Saya hanya memasukkan itu sebagai contoh tentang apa yang ingin saya lakukan; Saya telah mengeditnya dari pertanyaan untuk menghindari kebingungan.
DNS
Itu mendaftar semua atribut dari objek yang sudah diimpor, bukan daftar sub-modul saja. Jadi itu tidak menjawab pertanyaan itu.
bignose