inspect モジュールでオブジェクトの情報を取得する
呼称: インスペクション機能の触り
目的: inspect モジュールの使用方法を学ぶ
特徴: ログ出力時に pprint モジュールを使うと整形されて見易い
用例: 実行中のソースの行番号を出力する、デバッグ
備考: inspect モジュールはもっと奥が深そう
Python で実行中のソースの行番号を出力する仕組み(C 言語でいう __LINE__ マクロがあるかどうか)を調べてみたら、幾つかの方法が分かりました。sys._getframe を使う方法もありますが、ここでは inspect モジュールの使用方法を紹介します。
シンプルな方法では、以下で現在行を取得できます。
inspect.currentframe().f_lineno
ある共通関数で、どこから呼び出されたか知りたい場合、以下で呼び出し元の行番号を取得できます。
inspect.currentframe().f_back.f_lineno
デバッグ等で、もっと詳細な情報を取得したい場合、inspect.stack を使うと面白いです。
inspect.stack(context)[frame][content]
context は、コンテキストとして取得する前後の総行数を指定します。以下の例では1なので1行のみを取得します。frame は、呼び出し元が最初の要素に入り、末尾が最も外側になります。以下の例では、get_inspect_stack が 0、get_inspect_stack を呼び出した場所のフレーム情報が 1、get_inspect_stack を呼び出した場所を含む関数を呼び出した場所のフレーム情報が 2 になります。content を指定することで、フレームオブジェクト、ファイル名、実行中の行番号、関数名、コンテキストのソース行のリスト、ソース行リストの実行中行のインデックスの6つの情報のどれかを取得できます。
と、私はリファレンスを読んでもよく分からなかったので、実際に動かして中身を覗いてみました。ちょっと触ってみただけですが、あんな情報やこんな情報も取れるんじゃないかとイメージが膨らんで面白いですね。
当初は行番号だけが知りたかったのですが、inspect.stack の中身を覗いているうちに、ネストするとどうなるのかなとか、ログに出力しようとか、きれいに整形しようとか、副次的なモチベーションが出てきて、最終的に以下の内容を検証しました(^ ^;;
#!/usr/bin/env python # -*- coding: utf-8 -*- import inspect import logging from pprint import pprint, pformat logfile = '/tmp/inspect.log' logging.basicConfig( level = logging.DEBUG, format = '%(asctime)s %(levelname)s %(message)s', datefmt = '%Y-%m-%d %H:%M:%S', filename = logfile, filemode = 'w') log = logging.getLogger("try_inspect") stack_content = [ 'frame obj ', 'file name ', 'line num ', 'function ', 'context ', 'index ', ] class MyClass(object): def __init__(self, arg): self.obj = arg def __add__(self, arg): log.debug(pformat(get_inspect_stack())) log.debug('self:%s, arg:%s' %( type(self.obj), type(arg) )) try: concat = self.obj + arg except: concat = None return concat def main(): alphabet = 'abc' hiragana = u'あいうえお' katakana = 'アイウエオ' # inspect class behavior my_alphabet = MyClass(alphabet) log.debug(my_alphabet + hiragana) my_hiragana = MyClass(hiragana) log.debug(my_hiragana + katakana) # inspect function behavior my_func() def get_inspect_stack(): context, frame = 1, 2 return dict(zip(stack_content, inspect.stack(context)[frame])) def my_func(): log.debug(pformat(get_inspect_stack())) def my_nested_func(): log.debug("Current line : %s", inspect.currentframe().f_lineno) log.debug(pformat(get_inspect_stack())) log.debug("Caller's line : %s", inspect.currentframe().f_back.f_lineno) def my_double_nested_func(): log.debug(pformat(get_inspect_stack())) my_double_nested_func() my_nested_func() if __name__ == '__main__': main()
実行結果。
$ python inspect_stack.py $ cat /tmp/inspect.log 2009-09-09 13:22:46 DEBUG {'context ': [' log.debug(my_alphabet + hiragana)\n'], 'file name ': 'inspect_stack.py', 'frame obj ': <frame object at 0x80f482c>, 'function ': 'main', 'index ': 0, 'line num ': 46} 2009-09-09 13:22:46 DEBUG self:<type 'str'>, arg:<type 'unicode'> 2009-09-09 13:22:46 DEBUG abcあいうえお 2009-09-09 13:22:46 DEBUG {'context ': [' log.debug(my_hiragana + katakana)\n'], 'file name ': 'inspect_stack.py', 'frame obj ': <frame object at 0x80f482c>, 'function ': 'main', 'index ': 0, 'line num ': 49} 2009-09-09 13:22:46 DEBUG self:<type 'unicode'>, arg:<type 'str'> 2009-09-09 13:22:46 DEBUG None 2009-09-09 13:22:46 DEBUG {'context ': [' my_func()\n'], 'file name ': 'inspect_stack.py', 'frame obj ': <frame object at 0x80f482c>, 'function ': 'main', 'index ': 0, 'line num ': 52} 2009-09-09 13:22:46 DEBUG Current line : 62 2009-09-09 13:22:46 DEBUG {'context ': [' my_nested_func()\n'], 'file name ': 'inspect_stack.py', 'frame obj ': <frame object at 0x80f91a4>, 'function ': 'my_func', 'index ': 0, 'line num ': 68} 2009-09-09 13:22:46 DEBUG Caller's line : 68 2009-09-09 13:22:46 DEBUG {'context ': [' my_double_nested_func()\n'], 'file name ': 'inspect_stack.py', 'frame obj ': <frame object at 0x810618c>, 'function ': 'my_nested_func', 'index ': 0, 'line num ': 67}
リファレンス:
http://pythonjp.sourceforge.jp/dev/library/inspect.html
http://mail.python.org/pipermail/python-list/2006-August/569510.html
Re: Python equivt of __FILE__ and __LINE__