追記:
quiquiさん回答からIPythonを知り、インストールして少し使ってみたところ・・・今まで知らずにいて損した気分がだんだん強くなってきました。自分の回答は蛇足だったようです。
(ちなみにIPythonのREPL上で定義した関数fooについて
foo.__code__.co_filename
を調べるとCPythonでは<stdin>
であったのに対してIPythonでは<ipython-input-4-01cc26abac88>
といった名前が入ってます。つまり元の回答で少し書いたlinecacheへのモンキーパッチをIPythonはやってくれているということだと思います。)
以下元の回答
CPython前提でのコメントです。
(CPythonはPython言語の最も広く用いられている標準の実装のことです: by Wikipedia)
おそらくREPL上で直接定義した関数についてはデフォルトの状態のままでは見ることができないと思います。inspect.getsource()は
関数オブジェクト.__code__.co_filename
関数オブジェクト.__code__.co_firstlineno
から元のソースを取り出してそれを表示しているのだと思います。この情報はモジュールをimportする場合など、CPythonのコンパイラーがソースファイルをコンパイルしてcodeオブジェクトに変換する際に記録されますがREPLのco_filenameは"<stdin>"などになっており、所謂普通のファイルパスではありません。
しかしながらinspect.pyを覗いてみると面白いコメントがありました。
Python
1def findsource(object):
2 """Return the entire source file and starting line number for an object.
3
4 The argument may be a module, class, method, function, traceback, frame,
5 or code object. The source code is returned as a list of all the lines
6 in the file and the line number indexes a line in that list. An OSError
7 is raised if the source code cannot be retrieved."""
8
9 file = getsourcefile(object)
10 if file:
11 # Invalidate cache if needed.
12 linecache.checkcache(file)
13 else:
14 file = getfile(object)
15 # Allow filenames in form of "<something>" to pass through.
16 # `doctest` monkeypatches `linecache` module to enable
17 # inspection, so let `linecache.getlines` to be called.
18 if not (file.startswith('<') and file.endswith('>')):
19 raise OSError('source code not available')
linecacheモジュールにモンキーパッチを当てれば'<...>'という形式の特別なファイルに対してソース行を取り出せるような仕組みを入れ込めるということだと解釈しました。linecache.pyをみると180行程度のモジュールでしたので、これを調べると方法が考え出せるかと思います。
しかし自分はREPLを使う際には別の工夫をします。
###IDLE
こういう目的ならIDLEのREPL+Editorを使うのもいいのではないでしょうか。ちょっと変数の中身を見たり関数の動きを試す際にはREPLを、関数を定義してrunとしたい場合はEditorを使うという感じです。
###python -i
IDLEのREPLはインデントがくずれたりとちょっとだけくせがある気がするのでpython(CPython)をターミナル上で起動してREPLとして用いたくなる場合もよくあります。その場合REPL自身のヒストリー機能は活用しますが、関数をREPL上で定義するのはやはり面倒なので別の画面でエディターを起動しておいて普通にソースファイル上に関数を定義します。
少しずつ変更しながら再度importするのがメンドクサイので、
py.py
python
1import sys
2import importlib
3import inspect # for debug
4import dis # for debug
5
6def reload():
7 # reload this module
8 new_py = importlib.reload(sys.modules['py'])
9
10 def is_not_builtin_name(an):
11 return not (an.startswith('_') or an.endswith('_'))
12
13 # このファイル上のアンダースコア以外の名前を__main__モジュール
14 # の名前空間上に上書きしてしまう
15
16 main = sys.modules['__main__']
17 for an in filter(is_not_builtin_name, dir(new_py)):
18 av = getattr(new_py, an)
19 if not inspect.ismodule(av):
20 setattr(main, an, av)
21 print(f'reloaded {an} {type(av)}')
22 main.py = new_py
というようなreload関数を定義しておき、REPL上からreload()
と打ち込んでしまいます。定義の中身自体はリロードしたモジュール上にある名前を__main__
モジュールへ上書きするというインチキくさいものなのでCPythonをREPLとして用いるときにしか使いません。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/04/29 12:00
2018/04/29 14:09