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

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

ただいまの
回答率

90.35%

  • Python 3.x

    7330questions

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

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 5
  • VIEW 459

yuyabu

score 51

 質問内容

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

 やりたいこと

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

 調べたこと

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

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

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

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

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

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

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

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

 環境

Python 3.6.4

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+6

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 21:00

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

    キャンセル

  • 2018/04/29 23:09

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

    キャンセル

+2

追記:
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を覗いてみると面白いコメントがありました。

def findsource(object):
    """Return the entire source file and starting line number for an object.

    The argument may be a module, class, method, function, traceback, frame,
    or code object.  The source code is returned as a list of all the lines
    in the file and the line number indexes a line in that list.  An OSError
    is raised if the source code cannot be retrieved."""

    file = getsourcefile(object)
    if file:
        # Invalidate cache if needed.
        linecache.checkcache(file)
    else:
        file = getfile(object)
        # Allow filenames in form of "<something>" to pass through.
        # `doctest` monkeypatches `linecache` module to enable
        # inspection, so let `linecache.getlines` to be called.
        if not (file.startswith('<') and file.endswith('>')):
            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

import sys
import importlib
import inspect  # for debug
import dis      # for debug

def reload():
    # reload this module
    new_py = importlib.reload(sys.modules['py'])

    def is_not_builtin_name(an):
        return not (an.startswith('_') or an.endswith('_'))

    # このファイル上のアンダースコア以外の名前を__main__モジュール
    # の名前空間上に上書きしてしまう

    main = sys.modules['__main__']
    for an in filter(is_not_builtin_name, dir(new_py)):
        av = getattr(new_py, an)
        if not inspect.ismodule(av):
            setattr(main, an, av)
            print(f'reloaded {an} {type(av)}')
    main.py = new_py


というようなreload関数を定義しておき、REPL上からreload()と打ち込んでしまいます。定義の中身自体はリロードしたモジュール上にある名前を__main__モジュールへ上書きするというインチキくさいものなのでCPythonをREPLとして用いるときにしか使いません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/29 21:14

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

    キャンセル

  • 2018/04/29 23:14

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

    キャンセル

  • 2018/04/29 23:22

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

    キャンセル

  • 2018/04/29 23:22

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

    キャンセル

  • 2018/04/29 23:31

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

    キャンセル

  • 2018/04/29 23:39

    > 本格的に何かを作る場所ではない

    それはおっしゃるとおりと思います。REPLはあくまで調べたり学んだりする目的が主だと思います。

    キャンセル

+1

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/29 20:05

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

    キャンセル

  • 2018/04/29 20:09

    「あるある」ですね

    キャンセル

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

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Python 3.x

    7330questions

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