利用可能なモジュール一覧を取得する

ふとPythonで利用可能なモジュールの一覧を取得したくなったのでメモ。

条件

ここでいう利用可能なモジュール および パッケージ一覧とは、以下のようなものとする。

  • ビルトインで組み込まれてるやつ
  • site-packages にインストールされてるやつ
  • virtualenvなどの仮想環境などにインストールされてるやつ
  • というかパスが通ってて、モジュール、パッケージとして認識されるヤツ全て
  • 確認したいのじゃなくてリストとかのデータとして扱いたい

こんなん僕が求めてるのとちゃう

求めてるものとはなんか違った夢の残骸達

sys.builtin_module_names

文字通り、ビルトインで入ってるやつの一覧。足りない

import sys                                                                      
print sys.builtin_module_names

#=> ('__builtin__', '__main__', '_ast', '_codecs', '_sre', '_symtable', '_warnings', '_weakref', 'errno', 'exceptions', 'gc', 'imp', 'marshal', 'posix', 'pwd', 'signal', 'sys', 'thread', 'xxsubtype', 'zipimport')
sys.modules

実行時にロード済みなやつの一覧。まだ足りない。とういかロードしてない組み込みのヤツは出てこない。

import sys                                                                      
print sys.modules.keys()

#=>['cStringIO', 'copy_reg', 'encodings', 'site', '__builtin__', '__main__', 'encodings.encodings', 'abc', 'posixpath', 'flaskext', '_weakrefset', 'errno', 'pprint', 'encodings.codecs', '_abcoll', 'types', '_codecs', 'new', '_warnings', 'genericpath', 'stat', 'zipimport', 'encodings.__builtin__', 'warnings', 'UserDict', 'encodings.utf_8', 'sys', 'codecs', 'os.path', 'signal', 'linecache', 'posix', 'encodings.aliases', 'exceptions', 'os', '_weakref']

help('modules')

めっさ一杯出た。大分近い。仮想環境にいれたヤツとかもちゃんと拾ってくれてる。。。けど結果が標準出力されるのでなんか微妙

$ python -c "help('modules')"

Please wait a moment while I gather a list of all available modules...

ArgImagePlugin      _Mlte               difflib             pimp
Audio_mac           _MozillaCookieJar   dir_help            pip
BaseHTTPServer      _OSA                dircache            pipes
Bastion             _Qd                 dis                 pkg_resources
BdfFontFile         _Qdoffs             distutils           pkgutil
BeautifulSoup       _Qt                 django_assets       platform
BeautifulSoupTests  _Res                doctest             plistlib
BmpImagePlugin      _Scrap              dprint              popen2
...
...

これや僕が求めてたのはこれなんや

python -c "help('modules')"」の結果は、「pydoc modules」と等価である。というかhelpは多分pydocを見てる。というわけで、pydoc.pyをあら探しする。

であった。それが「pydoc.ModuleScanner」とかいうヤツ。pydoc.pyではこんな使いかたしてた。

1879             modules = {}
1880             def callback(path, modname, desc, modules=modules): 
1881                 if modname and modname[-9:] == '.__init__': 
1882                     modname = modname[:-9] + ' (package)' 
1883                 if find(modname, '.') < 0: 
1884                     modules[modname] = 1 
1885             def onerror(modname):
1886                 callback(None, modname, None)
1887             ModuleScanner().run(callback, onerror=onerror)
1888             self.list(modules.keys())

これを使って「pydoc modules」は実現されていた。単純に一覧が欲しいだけので、コピってこんな感じに変えてみた。

#!/usr/bin/env python
# -*- coding: utf-8 -*

from pydoc import ModuleScanner
from string import find

modules = []

def callback(path, modname, desc, modules=modules):  
    if modname and modname[-9:] == '.__init__': 
        modname = modname[:-9]  
    if find(modname, '.') < 0 and modname not in modules:
        modules.append(modname) 

def onerror(modname): 
    callback(None, modname, None)

ModuleScanner().run(callback, onerror=onerror)
print modules

これを実行すると。。。

$ python all_modules.py
['__builtin__', '_ast', '_codecs', '_sre', '_symtable', '_warnings', '_weakref', 'errno', 'exceptions', 'gc', 'imp', 'marshal', 'posix', 'pwd', 'signal', 'sys', 'thread', 'xxsubtype', 'zipimport', 'all_avaliable_modules', 'all_avaliable_modules2', 'ascii_to_ebcidic', 'blinker_sample', 'bool_judge', 'cast', 'class_name', 'converting_byte', 'datetime_test', 'dict_key_map', 'dict_merge', 'diff_time', 'dir_help', 'dprint', 'fib', 'file_abs_path', 'fold_test', 'func_args_disruptiv
...
...

おー。いっぱいでてきた。ビルトインも、仮想環境も、カレントディレクトリのヤツも全部出てきたw

サブモジュール、パッケージも一覧に含める

上のヤツでは、トップレベル(と呼んでいいんだろうか?)のパッケージとモジュールの一覧しか表示されないようになってる。サブモジュール・パッケージも含めて一覧で表示したかったら。「callback」関数をこう変えればいい。

def callback(path, modname, desc, modules=modules):
    if modname and modname[-9:] == '.__init__':
        modname = modname[:-9]
-    if find(modname, '.') < 0  and modname not in modules:
-        modules.append(modname)
+    if modname not in modules:
+        modules.append(modname)

愚行

ModuleScannerに辿り着く前は、もういいやって気分で「pydoc modules」の標準出力をパースして強引にリストを取得しようとしている愚かな僕がいました。けど「sys.path.insert」で追加したパスのモジュールとか取得できないじゃんとか思ってやめた

#!/usr/bin/env python                                                           
# -*- coding: utf-8 -*-                                                         

import pprint
import commands
import re
ret = commands.getoutput('python -c "help(\'modules\')"')
module_list = []
start = False
for line in ret.split('\n'): 
    if re.match(r'[^ ]+[ ]+[^ ]+[ ]+[^ ]+[ ]+[^ ]+$', line): 
        start = True
    if start and line == '':
        break
    if not start:
        continue

    line = re.sub(r'[ ]+',r' ', line)  
    for module in line.split(' '):
        if module.strip(' ') != '': 
            module_list.append(module.strip(' '))

module_list.sort() 
pprint.pprint(module_list) 

追記(2015/3/4)

コメント欄で 教えて貰った「pkgutil.iter_modules」を使うとインストール済みのやつとかを一覧でみることができるようです。いい感じですね。:id:hirokiky さんありがとうございます。

>>> import pkgutil
>>> for m in pkgutil.iter_modules():
...     print m
...
(<pkgutil.ImpImporter instance at 0x103103e18>, 'UserDict', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, '_abcoll', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, '_weakrefset', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, 'abc', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, 'codecs', False)

余談

  • ModuleScannerのonerrorという関数は、何かしらのエラーでimportできないパッケージに対して呼び出される。(モジュールではなく)