読者です 読者をやめる 読者になる 読者になる

inspect モジュールでオブジェクトの情報を取得する

python

呼称: インスペクション機能の触り
目的: 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__