インタープリタのスタックフレームにアクセスし、メソッドの呼び出し元のオブジェクトを取得する
いまさらながらPythonにハマっています。Google App Engineが超楽しいですね。さて今日は、GAEではなくて、Pythonの言語機能を紹介します。環境はPython2.6.1、Mac OSX 10.6.1で試しました。
Pythonではinspectモジュールを使うことで、インタープリタのスタックフレームにアクセスすることができます。これを使って、Pythonで呼び出し元のオブジェクトを取得してみます。
#!/usr/bin/env python # -*- coding: utf-8 -*- import inspect def caller(): try: # フレームレコードのリストを取得する framerecords = inspect.stack() # 二つ前のフレームレコードを取得する fremerecord = fremerecords[2] # タプルからフレームを取得する frame = framerecord[0] # 指定したフレームの引数の情報を取得する arginfo = inspect.getargvalues(frame) # localsの辞書に格納されたselfオブジェクトを取得する return arginfo.locals['self'] if 'self' in arginfo.locals else None finally: del frame return None class Hoge(): def methodA(self): obj = caller() print obj # <__main__.Foo instance at 0x1d5620> obj.hoo() class Foo(): def __init__(self): self.hoge = Hoge() def foo(self): self.hoge.methodA() def hoo(self): print "hoo" foo = Foo() foo.foo()
caller()メソッドが、呼び出し元のオブジェクトを取得するメソッドです。それではソースコードをみてみます。
最初に呼び出し元スタックのフレームレコードのリストを取り出します。
# フレームレコードのリストを取得する
framerecords = inspect.stack()
callerメソッド内からみると取得したいスタックフレームレコードは2つ前のスタックフレームレコードなので、framerecords[2]でメソッドを呼び出したフレームレコードが取得できます。
# 二つ前のフレームレコードを取得する fremerecord = fremerecords[2]
このフレームレコードは、は長さ6のタプルで、このような構成になっています。
(フレームオブジェクト, ファイル名, 実行中の行番号, 関数名, コンテキストのソース行のリスト, ソース行リストの実行中行のインデックス)
フレームレコードからフレームオブジェクトを取得します。
# タプルからフレームを取得する frame = framerecord[0]
フレームオブジェクトから、指定されたフレームに渡された引数の情報を取得します。
# 指定したフレームの引数の情報を取得する
arginfo = inspect.getargvalues(frame)
引数の情報は、長さ4のタプルで次の値を返します:
(引数, キーワード引数, 引数の名前, ローカル変数の辞書)
そこからローカル変数の辞書からselfと呼ばれているオブジェクトを取得すると、呼び出し元のオブジェクトが取得できます。
# localsの辞書に格納されたselfオブジェクトを取得する return arginfo.locals['self'] if 'self' in arginfo.locals else None
最後に循環参照にならないようfinallyブロックでフレームを削除したら処理完了です。
実行結果はfoo->hoge->hooと呼び出しています。

使い道はあんまりなさそうだなあ。ダブルディスパッチをcalleeを渡さずに実行できるぐらいかも。
javaでできるとフレームワークの制限を打ち破れるので結構楽しいかも。