質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

5328閲覧

Pythonで対話インタプリタ上で宣言した関数の実装を見る方法

yuyabu

総合スコア63

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

5クリップ

投稿2018/04/29 10:51

編集2018/04/29 10:55

質問内容

Pythonの対話インタプリタ上で宣言・実装した関数のソースコードを見る方法があれば教えてください。

やりたいこと

Pythonの対話インタプリタ(REPL)を学習に使っています。
REPL上で宣言した関数の実行がうまくいかず、実装内容を表示してtypoなど調べようと思ったのですが、うまく表示できませんでした。

調べたこと

JavaScriptの対話環境(Chromeの開発ツール)ではメソッド呼び出しなし,つまり()なしで関数を書くと実装を表示してくれますが、

js

1>function test(){console.log('test')} //メソッドの宣言 2undefined 3>test 4ƒ test(){console.log('test')}

これと同じことがPythonで出来ないかなと思い調べてみました。

https://docs.python.jp/3/library/inspect.html

どうやら inspect.getsource(object)でできるようです。
試しにnumpy.sumで試してみました。改行が\nになっていますが一応文字列でnumpy.sumの実装が戻ってきました。

自分で定義した関数に使ってみたところ、

Python

1>>> def test(): #調査対象の関数の宣言 2... print('test') 3>>> inspect.getsource(test) #実行(失敗する) 4Traceback (most recent call last): 5 File "<stdin>", line 1, in <module> 6 File "/anaconda3/lib/python3.6/inspect.py", line 968, in getsource 7 lines, lnum = getsourcelines(object) 8 File "/anaconda3/lib/python3.6/inspect.py", line 955, in getsourcelines 9 lines, lnum = findsource(object) 10 File "/anaconda3/lib/python3.6/inspect.py", line 786, in findsource 11 raise OSError('could not get source code') 12OSError: could not get source code 13>>>

使えないようです。
対話インタプリタ上で宣言した関数のソースを見る方法はないのでしょうか?

環境

Python 3.6.4

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

macOS pyenv anaconda3-4.3.1(Python3.6.1) の python REPL で、質問の通りにエラーが発生しました。

anacondaを使っているようですので、python REPLではなくてipythonを使うといいと思います。
inspect.getsourceも使えるようですし、??ディレクティブを使ってもいいです。

% ipython Python 3.6.2 |Anaconda custom (x86_64)| (default, Jul 20 2017, 13:14:59) Type 'copyright', 'credits' or 'license' for more information IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import inspect In [2]: def test(): ...: print('test') ...: In [3]: inspect.getsource(test) Out[3]: "def test():\n print('test')\n" In [4]: ??test Signature: test() Source: def test(): print('test') File: ~/<ipython-input-2-c19776d75b84> Type: function

投稿2018/04/29 11:31

quickquip

総合スコア11038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2018/04/29 12:00

うーむ。ipythonいいかもですね。
yuyabu

2018/04/29 14:09

回答ありがとうございます。こちら手元のipythonで試したところ再現できました!
guest

0

追記:
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 11:55

編集2018/04/29 14:30
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2018/04/29 12:14

quiquiさんコメントにあるipython。これを使うのが一番正解な気がしました。自分の回答は不要ですね。
yuyabu

2018/04/29 14:14

回答ありがとうございます。 linecacheやcpythonなど知らない技術要素が多いですが、多分そもそも私の運用がイレギュラーなものなのでIDLEやpycharmなどのIDEを使うべきなのでしょうね。
KSwordOfHaste

2018/04/29 14:22

イレギュラーではないと思いますよ。せっかくのインタープリタなんですし、REPLを積極的に使うのは大変結構だと思います。ただ追記したようにREPLとしてはcpythonなどをそのまま使うよりipythonをフロントエンドとして使った方が大分使い勝手がよさそうですね。自分は今後IDLE, PyCharm, IPython, cpythonをそれぞれ場面によって使い分けようと思いました。
quickquip

2018/04/29 14:22

ipythonは中で何かやってんだろうなぁ、ぐらいにしか思わなかったので、この情報はありがたいです。
yuyabu

2018/04/29 14:31

>イレギュラーではないと思いますよ。 そうなのですか。pythonはまだまだ初心者で仕事などで本格的に使ったことはありませんが、REPLは初心者が学習する砂場的なもであり、そこで本格的に何かを作る場所ではないという印象を持っています。
KSwordOfHaste

2018/04/29 14:39

> 本格的に何かを作る場所ではない それはおっしゃるとおりと思います。REPLはあくまで調べたり学んだりする目的が主だと思います。
guest

0

素直に上スクロールするか、表示されている画面上で検索かければ良いと思いますが・・・。
私はそのものずばりの方法は知りませんが、バイトコードディスアセンブラは知っています。憶測だけど関数が定義された瞬間にバイトコードにされて、元の表現はデータとして残らない気がするのですが、どうなんでしょう。詳しい人に聞いてみたいです。

python

1>>> import dis 2>>> def test(): 3... print("hoge") 4... 5>>> dis.dis(test) 6 2 0 LOAD_GLOBAL 0 (print) 7 3 LOAD_CONST 1 ('hoge') 8 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 9 9 POP_TOP 10 10 LOAD_CONST 0 (None) 11 13 RETURN_VALUE

参考:
32.12. dis — Python バイトコードの逆アセンブラ — Python 3.6.5 ドキュメント

投稿2018/04/29 11:00

hayataka2049

総合スコア30933

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuyabu

2018/04/29 11:05

hayataka2049さん回答ありがとうございます >素直に上スクロールするか、表示されている画面上で検索かければ良いと思いますが・・・。 その時は巨大な配列を間違えて表示してしまったのでスクロールはちょっと難しいような状況でした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問