関数の呼び出し元のファイル名と行番号

開発中に関数の呼び出し元のファイル名・行番号を知りたくなることがあります。例えばエラーの際に開発者にメールを送ることにして、そのメールがどのファイルのどこから送信されたものかを知りたい時などがそれにあたります…と言っても言葉だけでは伝わりづらそうなので、こんな擬似コードを御覧ください。

def send_mail(subject, body, address_from, address_to):
    # 本当はメールを送る処理をする
    print '<<< SUBJECT >>>'
    print subject
    print ''
    print '<<< BODY >>>'
    print body
    print ''
    print 'FROM = [%s]' % address_from
    print 'TO = [%s]' % ' / '.join(address_to)

def warn_to_developers(subject, *args, **kwargs):
    send_mail(
        '[WARNING] %s' % subject,
        '\n'.join([
            '=> args',
            '\n'.join(args),
            '=> kwargs',
            '\n'.join(['%s = [%s]' % (k, v) for k, v in kwargs.items()]),
            '--',
            'mail from',
            '    %s' % 'TODO: ここに呼び出し元のファイル名を入れたい',
            '    %s' % 'TODO: ここに呼び出し元の行番号を入れたい',
        ]),
        'warning@example.com',
        ['kawasaki@example.com'],
    )

def hoge(a, b):
    if not a != b:
        warn_to_developers(
            'この味は!',
            'ウソをついている「味」だぜ…',
            a=a,
            b=b,
        )
    assert a != b, '%s and %s must NOT be same.' % (a, b)

hoge(1, 2)
hoge(9, 9)

上記擬似コードにおいて、TODO と書かれた場所が2箇所あります。ここに「呼び出し元のファイル名と行番号」を入れることができると、まぁデバッグ時(というかリリース後の不具合監視)に便利だったりするわけです。そんなときはこう書くようにします。

import inspect

def send_mail(subject, body, address_from, address_to):
    # 本当はメールを送る処理をする
    print '<<< SUBJECT >>>'
    print subject
    print ''
    print '<<< BODY >>>'
    print body
    print ''
    print 'FROM = [%s]' % address_from
    print 'TO = [%s]' % ' / '.join(address_to)

def warn_to_developers(subject, *args, **kwargs):
    send_mail(
        '[WARNING] %s' % subject,
        '\n'.join([
            '=> args',
            '\n'.join(args),
            '=> kwargs',
            '\n'.join(['%s = [%s]' % (k, v) for k, v in kwargs.items()]),
            '--',
            'mail from',
            '    %s' % inspect.currentframe().f_back.f_code.co_filename,
            '    %s' % inspect.currentframe().f_back.f_lineno,
       ]),
        'warning@example.com',
        ['kawasaki@example.com'],
    )

def hoge(a, b):
    if not a != b:
        warn_to_developers(
            'この味は!',
            'ウソをついている「味」だぜ…',
            a=a,
            b=b,
        )
    assert a != b, '%s and %s must NOT be same.' % (a, b)

hoge(1, 2)
hoge(9, 9)

どう書けば良いのか、なかなかこれといった書き方が今まで定まっていなかったのですが、つい先日この書き方を見つけて便利だったのでメモ代わりに書いてみました。