オブジェクトの情報を探る(8) - フレームオブジェクト

【 目次 】

実行時の状態の情報を得る - フレーム (frame) オブジェクト

実行フレーム、すなわち、ある時点での実行時の状態のデータという事かな。
フレームオブジェクトとはコールスタックスタックフレームの状態を保持しているオブジェクトの事を指すようだ。
という事で、コールスタックとは

  • コールスタック - Wikipedia

    コールスタック (Call Stack)は、プログラムの実行中にサブルーチンに関する情報を格納するスタックである。
    ...
    コールスタックを使う目的はいくつかあるが、主たる目的は実行中サブルーチンの処理を完了して制御を戻す(呼び出し側に戻る)ときに、どこに戻ればよいかを覚えておくことである。
    ...
    コールスタックはスタックフレームから構成される(アクティベーションレコードとも呼ばれる)。スタックフレームはマシン依存のデータ構造であり、サブルーチンの状態情報が格納される。各スタックフレームは完了していないサブルーチン呼び出しに対応する。

プログラムの実行中に関数等のCallableなオブジェクトが呼ばれた場合、いわゆるサブルーチンに処理が移動する。
そしてサブルーチンの処理が終了したら、呼び出し元のルーチンから処理を再開できるようにジャンプする前の状態を覚えている必要があって、その実行状態をコールスタックという場所に保存しておいて後で取り出せるような仕組みになっている。
その実行状態が保存されているオブジェクトの事をフレームオブジェクトと言って、サブルーチンが呼ばれる度に、スタックにその時の状態を保持するフレームオブジェクトがスタックに積まれていって、そのひとつひとつのスタックに積まれたフレームオブジェクトの事をスタックフレームというのかな。

スタックというのは

つまり、処理を再開するには、最後にスタックに積んだ実行フレームから順番に取り出す事で、何重にもジャンプしてもちゃんと呼び出し順に処理を再開できるって事かな。

フレームオブジェクトの属性

フレームオブジェクトで何がアクセスできるかというと
公式ドキュメントの表より、フレームオブジェクトの部分より抜き出すと

属性 説明
f_back 外側 (このフレームを呼び出した) のフレームオブジェクト
f_builtins このフレームで参照している組み込み名前空間
f_code このフレームで実行しているコードオブジェクト
f_exc_traceback このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None
f_exc_type このフレームで例外が発生した場合には例外型、それ以外なら None
f_exc_value このフレームで例外が発生した場合には例外の値、それ以外なら None
f_globals このフレームで参照しているグローバル名前空間
f_lasti 最後に実行しようとしたバイトコードのインデックス
f_lineno 現在の Python ソースコードの行番号
f_locals このフレームで参照しているローカル名前空間
f_restricted 制限実行モードなら1、それ以外なら0
f_trace このフレームのトレース関数、または None

f_exc_type,f_exc_value,f_exc_tracebackは、それぞれ例外発生時の例外型,例外の値,トレースバックオブジェクトを示す。
これらについては、「エラー,例外処理とトレースバックオブジェクト」を参照。

フレームオブジェクトの操作

現在のスタックフレームのフレームオブジェクトを取得

現在のスタックフレームのフレームオブジェクトを取得するには以下の2通りの方法がある。

inspectモジュールのcurrentframe関数を利用して

inspect.currentframe()

もしくは、sysモジュールの_getframe関数を利用して

sys._getframe()

sysモジュールの_getframe関数の場合には、depth引数を指定して「スタックのトップから depth だけ下」のフレームオブジェクトを取得する事もできる。

フレームオブジェクトの情報を表示してみる。

フレームオブジェクトの情報を表示してみる。
以下のコードは関数func_a内の現在のスタックフレームのフレームオブジェクトの情報を表示している。

#coding: UTF-8

frame_attr_dict = {
    "f_back":u"外側 (このフレームを呼び出した) のフレームオブジェクト",
#    "f_builtins":u"このフレームで参照している組み込み名前空間",
    "f_code":u"このフレームで実行しているコードオブジェクト",
    "f_exc_traceback":u"このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None",
    "f_exc_type":u"このフレームで例外が発生した場合には例外型、それ以外なら None",
    "f_exc_value":u"このフレームで例外が発生した場合には例外の値、それ以外なら None",
#    "f_globals":u"このフレームで参照しているグローバル名前空間",
    "f_lasti":u"最後に実行しようとしたバイトコードのインデックス",
    "f_lineno":u"現在の Python ソースコードの行番号",
    "f_locals":u"このフレームで参照しているローカル名前空間",
    "f_restricted":u"制限実行モードなら1、それ以外なら0",
#    "f_trace":u"このフレームのトレース関数、または None",
 }

def func_a():
    import inspect
    frame_object=inspect.currentframe()
    code_object=frame_object.f_code
    print code_object.co_name
    for attr_name, attr_discript in sorted(frame_attr_dict.iteritems()):
        print attr_name, "=", getattr(frame_object, attr_name), "#",attr_discript
    source_text = inspect.getsource(code_object)
    print source_text

まず最初にフレームオブジェクトの属性とその内容を辞書として定義している。
関数func_a内で現在のフレームオブジェクトを取得する。
そして、フレームオブジェクトのf_code属性よりコードオブジェクトを取得,
コードオブジェクトが定義されたときの名前を表示,
辞書frame_attr_dictを使ってフレームオブジェクトの属性を取り出し表示,
最後にコードオブジェクトを使ってgetsource関数により現在のフレームのソースを表示している。

関数func_aを呼び出して実行してみる。

func_a()

実行結果

func_a
f_back = <frame object at 0x02763B30> # 外側 (このフレームを呼び出した) のフレームオブジェクト
f_code = <code object func_a at 0278C0B0, file "ソースファイル名(フルパス)", line 18> # このフレームで実行しているコードオブジェクト
f_exc_traceback = None # このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None
f_exc_type = None # このフレームで例外が発生した場合には例外型、それ以外なら None
f_exc_value = None # このフレームで例外が発生した場合には例外の値、それ以外なら None
f_lasti = 89 # 最後に実行しようとしたバイトコードのインデックス
f_lineno = 24 # 現在の Python ソースコードの行番号
f_locals = {'attr_name': 'f_locals', 'attr_discript': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u53c2\u7167\u3057\u3066\u3044\u308b\u30ed\u30fc\u30ab\u30eb\u540d\u524d\u7a7a\u9593', 'inspect': <module 'inspect' from 'C:\Python27\lib\inspect.pyc'>, 'code_object': <code object func_a at 0278C0B0, file "ソースファイル名(フルパス)", line 18>, 'frame_object': <frame object at 0x0253D900>} # このフレームで参照しているローカル名前空間
f_restricted = False # 制限実行モードなら1、それ以外なら0
def func_a():
    import inspect
    frame_object=inspect.currentframe()
    code_object=frame_object.f_code
    print code_object.co_name
    for attr_name, attr_discript in sorted(frame_attr_dict.iteritems()):
        print attr_name, "=", getattr(frame_object, attr_name), "#",attr_discript
    source_text = inspect.getsource(code_object)
    print source_text

sys._getframe()も使えるので、func_aの最初の2行は以下のように置き換える事もできる。

    import inspect,sys
    frame_object=sys._getframe()

フレームオブジェクトをたどる

フレームオブジェクトのf_back属性により、関数などの呼び出し元のフレームオブジェクトを取得できる。
そして、呼び出し元のフレームオブジェクトが存在しない場合には値はNoneとなる。
これを利用して、呼び出し元のフレームオブジェクトを次々とたどって、フレームオブジェクトの状態を表示してみよう。

def func_a():
    import inspect
    frame_object=inspect.currentframe()
    while frame_object != None :
        print "=================="
        code_object=frame_object.f_code
        print code_object.co_name
        for attr_name, attr_discript in sorted(frame_attr_dict.iteritems()):
            print attr_name, "=", getattr(frame_object, attr_name), "#",attr_discript
        frame_object=frame_object.f_back
        source_text = inspect.getsource(code_object)
        print source_text

def func_b():
    func_a()

def func_c():
    func_b()

func_c()

このプログラムでは関数func_cが関数func_bを呼び出し,さらに関数func_bが関数func_aを呼び出している。
関数が呼び出された分だけフレームオブジェクトが生成されスタックに積まれていることになる。

実行結果

==================
func_a
f_back = <frame object at 0x0000000001D60528> # 外側 (このフレームを呼び出した) のフレームオブジェクト
f_code = <code object func_a at 000000000246E4B0, file "C:\Rd\Python\pleiades-e4.4-python-jre_20140926\pleiades\workspace\Python2Test\src\frame1.py", line 18> # このフレームで実行しているコードオブジェクト
f_exc_traceback = None # このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None
f_exc_type = None # このフレームで例外が発生した場合には例外型、それ以外なら None
f_exc_value = None # このフレームで例外が発生した場合には例外の値、それ以外なら None
f_lasti = 109 # 最後に実行しようとしたバイトコードのインデックス
f_lineno = 26 # 現在の Python ソースコードの行番号
f_locals = {'attr_discript': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u53c2\u7167\u3057\u3066\u3044\u308b\u30ed\u30fc\u30ab\u30eb\u540d\u524d\u7a7a\u9593', 'code_object': <code object func_a at 000000000246E4B0, file "C:\Rd\Python\pleiades-e4.4-python-jre_20140926\pleiades\workspace\Python2Test\src\frame1.py", line 18>, 'attr_name': 'f_locals', 'frame_object': <frame object at 0x00000000024EE600>, 'inspect': <module 'inspect' from 'C:\Rd\Python\pleiades-e4.4-python-jre_20140926\pleiades\python\2\lib\inspect.pyc'>} # このフレームで参照しているローカル名前空間
f_restricted = False # 制限実行モードなら1、それ以外なら0
def func_a():
    import inspect
    frame_object=inspect.currentframe()
    while frame_object != None :
        print "=================="
        code_object=frame_object.f_code
        print code_object.co_name
        for attr_name, attr_discript in sorted(frame_attr_dict.iteritems()):
            print attr_name, "=", getattr(frame_object, attr_name), "#",attr_discript
        frame_object=frame_object.f_back
        source_text = inspect.getsource(code_object)
        print source_text

==================
func_b
f_back = <frame object at 0x0000000001D601E8> # 外側 (このフレームを呼び出した) のフレームオブジェクト
f_code = <code object func_b at 00000000024FEBB0, file "C:\Rd\Python\pleiades-e4.4-python-jre_20140926\pleiades\workspace\Python2Test\src\frame1.py", line 31> # このフレームで実行しているコードオブジェクト
f_exc_traceback = None # このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None
f_exc_type = None # このフレームで例外が発生した場合には例外型、それ以外なら None
f_exc_value = None # このフレームで例外が発生した場合には例外の値、それ以外なら None
f_lasti = 3 # 最後に実行しようとしたバイトコードのインデックス
f_lineno = 32 # 現在の Python ソースコードの行番号
f_locals = {} # このフレームで参照しているローカル名前空間
f_restricted = False # 制限実行モードなら1、それ以外なら0
def func_b():
    func_a()

==================
func_c
f_back = <frame object at 0x0000000001D56048> # 外側 (このフレームを呼び出した) のフレームオブジェクト
f_code = <code object func_c at 000000000257A3B0, file "C:\Rd\Python\pleiades-e4.4-python-jre_20140926\pleiades\workspace\Python2Test\src\frame1.py", line 34> # このフレームで実行しているコードオブジェクト
f_exc_traceback = None # このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None
f_exc_type = None # このフレームで例外が発生した場合には例外型、それ以外なら None
f_exc_value = None # このフレームで例外が発生した場合には例外の値、それ以外なら None
f_lasti = 3 # 最後に実行しようとしたバイトコードのインデックス
f_lineno = 35 # 現在の Python ソースコードの行番号
f_locals = {} # このフレームで参照しているローカル名前空間
f_restricted = False # 制限実行モードなら1、それ以外なら0
def func_c():
    func_b()

==================
<module>
f_back = None # 外側 (このフレームを呼び出した) のフレームオブジェクト
f_code = <code object <module> at 000000000257A130, file "C:\Rd\Python\pleiades-e4.4-python-jre_20140926\pleiades\workspace\Python2Test\src\frame1.py", line 3> # このフレームで実行しているコードオブジェクト
f_exc_traceback = None # このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None
f_exc_type = None # このフレームで例外が発生した場合には例外型、それ以外なら None
f_exc_value = None # このフレームで例外が発生した場合には例外の値、それ以外なら None
f_lasti = 99 # 最後に実行しようとしたバイトコードのインデックス
f_lineno = 37 # 現在の Python ソースコードの行番号
f_locals = {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Rd\\Python\\pleiades-e4.4-python-jre_20140926\\pleiades\\workspace\\Python2Test\\src\\frame1.py', '__package__': None, 'frame_attr_dict': {'f_lineno': u'\u73fe\u5728\u306e Python \u30bd\u30fc\u30b9\u30b3\u30fc\u30c9\u306e\u884c\u756a\u53f7', 'f_exc_traceback': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u4f8b\u5916\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u306b\u306f\u30c8\u30ec\u30fc\u30b9\u30d0\u30c3\u30af\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3001\u305d\u308c\u4ee5\u5916\u306a\u3089 None', 'f_code': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u5b9f\u884c\u3057\u3066\u3044\u308b\u30b3\u30fc\u30c9\u30aa\u30d6\u30b8\u30a7\u30af\u30c8', 'f_exc_type': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u4f8b\u5916\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u306b\u306f\u4f8b\u5916\u578b\u3001\u305d\u308c\u4ee5\u5916\u306a\u3089 None', 'f_back': u'\u5916\u5074 (\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3092\u547c\u3073\u51fa\u3057\u305f) \u306e\u30d5\u30ec\u30fc\u30e0\u30aa\u30d6\u30b8\u30a7\u30af\u30c8', 'f_lasti': u'\u6700\u5f8c\u306b\u5b9f\u884c\u3057\u3088\u3046\u3068\u3057\u305f\u30d0\u30a4\u30c8\u30b3\u30fc\u30c9\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9', 'f_restricted': u'\u5236\u9650\u5b9f\u884c\u30e2\u30fc\u30c9\u306a\u30891\u3001\u305d\u308c\u4ee5\u5916\u306a\u30890', 'f_exc_value': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u4f8b\u5916\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u306b\u306f\u4f8b\u5916\u306e\u5024\u3001\u305d\u308c\u4ee5\u5916\u306a\u3089 None', 'f_locals': u'\u3053\u306e\u30d5\u30ec\u30fc\u30e0\u3067\u53c2\u7167\u3057\u3066\u3044\u308b\u30ed\u30fc\u30ab\u30eb\u540d\u524d\u7a7a\u9593'}, '__name__': '__main__', 'func_a': <function func_a at 0x0000000002529588>, '__doc__': None, 'func_c': <function func_c at 0x0000000002529668>, 'func_b': <function func_b at 0x00000000025295F8>} # このフレームで参照しているローカル名前空間
f_restricted = False # 制限実行モードなら1、それ以外なら0
#coding: UTF-8

frame_attr_dict = {
    "f_back":u"外側 (このフレームを呼び出した) のフレームオブジェクト",
#    "f_builtins":u"このフレームで参照している組み込み名前空間",
    "f_code":u"このフレームで実行しているコードオブジェクト",
    "f_exc_traceback":u"このフレームで例外が発生した場合にはトレースバックオブジェクト、それ以外なら None",
    "f_exc_type":u"このフレームで例外が発生した場合には例外型、それ以外なら None",
    "f_exc_value":u"このフレームで例外が発生した場合には例外の値、それ以外なら None",
#    "f_globals":u"このフレームで参照しているグローバル名前空間",
    "f_lasti":u"最後に実行しようとしたバイトコードのインデックス",
    "f_lineno":u"現在の Python ソースコードの行番号",
    "f_locals":u"このフレームで参照しているローカル名前空間",
    "f_restricted":u"制限実行モードなら1、それ以外なら0",
#    "f_trace":u"このフレームのトレース関数、または None",
 }

def func_a():
    import inspect
    frame_object=inspect.currentframe()
    while frame_object != None :
        print "=================="
        code_object=frame_object.f_code
        print code_object.co_name
        for attr_name, attr_discript in sorted(frame_attr_dict.iteritems()):
            print attr_name, "=", getattr(frame_object, attr_name), "#",attr_discript
        frame_object=frame_object.f_back
        source_text = inspect.getsource(code_object)
        print source_text

インタープリタスタックをめぐる関数郡

inspectモジュールには、インタープリタスタック (たぶんpythonインタープリタのコールスタックや例外発生時のトレースバック等を指すのだろう)を操作する関数がいくつか用意されている。

インタープリタスタックでは、フレームレコードと呼ばれるレコード(データ)を使ってスタックフレームを操作する。
フレームレコードは長さ6のタプルで、「フレームオブジェクト」,「ファイル名」,「実行中の行番号」,「関数名」,「コンテキストのソース行のリスト」,「ソース行のリストにおける実行中の行のインデックス」が格納されている。

getframeinfo関数

getframeinfo関数はフレームオブジェクトを引数にとり、inspect.Traceback型のオブジェクトを返す。
inspect.Traceback型はTraceback(filename, lineno, function, code_context, index) 形式の名前付きタプル (named tuple)として実装されていて、フレームレコードの先頭要素のフレームオブジェクトを除いた値を、配列(タプル)のようにアクセスしたりクラスの属性としてアクセスしたりする事ができる。

getframeinfo関数を使って、フレームレコードの情報を表示してみる。

frame_object=inspect.currentframe()
inspect_traceback=inspect.getframeinfo(frame_object)
print type(inspect_traceback)
print inspect_traceback

実行結果

<class 'inspect.Traceback'>
Traceback(filename='ソースファイルのファイル名(フルパス)', lineno=6, function='<module>', code_context=['inspect_traceback=inspect.getframeinfo(frame_object)\n'], index=0)

フレームオブジェクトをたどる」で紹介したフレームの情報を表示するプログラムを、getframeinfo関数を使って修正してみると。

#coding: UTF-8

import inspect

def show_frameinfo(frameinfo):
    print "=================="
    print "filename=",frameinfo.filename
    print "lineno=",frameinfo.lineno
    print "function=",frameinfo.function
    print "code_context=",frameinfo.code_context
    print "index",frameinfo.index

def func_a():
    frame_object = inspect.currentframe()
    while frame_object != None :
        show_frameinfo(inspect.getframeinfo(frame_object))
        frame_object = frame_object.f_back

def func_b():
    func_a()

def func_c():
    func_b()

func_c()

実行結果

==================
filename= ソースファイルのファイル名(フルパス)
lineno= 16
function= func_a
code_context= ['        show_frameinfo(inspect.getframeinfo(frame_object))\n']
index 0
==================
filename= ソースファイルのファイル名(フルパス)
lineno= 20
function= func_b
code_context= ['    func_a()\n']
index 0
==================
filename= ソースファイルのファイル名(フルパス)
lineno= 23
function= func_c
code_context= ['    func_b()\n']
index 0
==================
filename= ソースファイルのファイル名(フルパス)
lineno= 25
function= <module>
code_context= ['func_c()\n']
index 0

このコードでは、フレームレコードの先頭要素を名前付きの要素(オブジェクトの属性)としてアクセスしているが、タプルの要素としてインデックス番号を使ってアクセスすると、show_frameinfo関数のコードは次のように書き換えることができる。

def show_frameinfo(frameinfo):
    print "=================="
    print "filename=",frameinfo[0]
    print "lineno=",frameinfo[1]
    print "function=",frameinfo[2]
    print "code_context=",frameinfo[3]
    print "index",frameinfo[4]

getouterframes関数

getouterframes関数を使うと、指定したフレームの外側(呼び出し元)の全フレームのフレームレコードのリストを一度に取得する事ができる。

上記のコードの関数func_aを、getouterframes関数を使って書き直すと以下のようになる。

def func_a():
    frame_object = inspect.currentframe()
    frame_records = inspect.getouterframes(frame_object)
    for frame_object,filename,lineno,function,code_context,index in frame_records:
        print "=================="
        print "filename=",filename
        print "lineno=",lineno
        print "function=",function
        print "code_context=",code_context
        print "index",index

stack関数 - スタックフレームの全フレームレコードを取得する。

stack関数は、現在のスタックを先頭にスタックフレームの全フレームレコードを一度に取得する事ができる。

関数func_aをstack関数を使って書き直すと、関数の最初の部分は以下のコードに置き換える事ができる。

def func_a():
    frame_records = inspect.stack()
    for frame_object,filename,lineno,function,code_context,index in frame_records:
        ...

getargvalues関数 - スタックフレームの引数についての情報を得る。

フレームオブジェクトを引数にしてgetargvalues関数を呼び出すと、そのフレームにおける関数の引数についての情報を取得する事ができる。

getargvalues関数はinspect.ArgInfo型(ArgInfo(args, varargs, keywords, locals)形式の名前付きタプル)の値を返す。

import inspect

def func(arg1, arg2,):
    frame_object = inspect.currentframe()
    argvalues = inspect.getargvalues(frame_object)

    print "type(argvalues)=", type(argvalues)
    print "argvalues=", argvalues
    print "argvalues.args=", argvalues.args
    print "argvalues.varargs=", argvalues.varargs
    print "argvalues.keywords=", argvalues.keywords
    print "argvalues.locals=", argvalues.locals

func("ARG1", "ARG2")

実行結果

type(argvalues)= <class 'inspect.ArgInfo'>
argvalues= ArgInfo(args=['arg1', 'arg2'], varargs=None, keywords=None, locals={'arg1': 'ARG1', 'arg2': 'ARG2', 'frame_object': <frame object at 0x00000000025D43D8>})
argvalues.args= ['arg1', 'arg2']
argvalues.varargs= None
argvalues.keywords= None
argvalues.locals= {'arg1': 'ARG1', 'arg2': 'ARG2', 'frame_object': <frame object at 0x00000000025D43D8>}

例外発生時のスタックトレースのフレームレコードを取得する関数

inspectモジュールには例外発生時のスタックトレースのフレームレコードを取得する関数も用意されているが、これについては次の「エラー,例外処理とトレースバックオブジェクト」で述べる事にする。

ページのトップへ戻る