「コード オブジェクト」のみにアクセスできるため、トレースバックからシンボルに直接アクセスする方法はありません。また、コード オブジェクトに関する Python ドキュメントには次のように書かれています。
関数オブジェクトとは異なり、コード オブジェクトは不変であり、変更可能なオブジェクトへの (直接的または間接的な) 参照を含みません。
トレースバックに関係するモジュール、関数、およびクラスを取得するには、それらを検索する必要があるようです。
動作するように見える実験バージョンがあります。この実装は、コード オブジェクトによって参照されるモジュールをウォークして、問題のコード オブジェクトを参照する関数またはメソッドを見つけることに基づいています。
from collections import namedtuple
import inspect
import sys
from nested.external import Foo
def inner(a, b='qux'):
"Inner function..."
return Foo().bar()
def outer(a, *args, **kwds):
return inner(a)
def resolve_signature(function):
"""Get a formatted string that looks like the function's signature."""
prgs, vrgs, kwds, defs = inspect.getargspec(function)
arguments = []
if defs:
for name in prgs[:len(defs)-1]:
arguments.append(name)
for i,name in enumerate(prgs[len(defs)-1]):
arguments.append('%s=%r'%(name,defs[i]))
else:
arguments.extend(prgs)
if vrgs:
arguments.append('*'+vrgs)
if kwds:
arguments.append('**'+kwds)
return '('+', '.join(arguments)+')'
def resolve_scope(module_name, code):
"""Resolve the scope name for a code object provided its module name."""
# Resolve module.
module = sys.modules.get(module_name, None)
if not module:
return '<hidden-module>' + '.' + code.co_name + '(?)'
# Check module's functions.
symbols = inspect.getmembers(module, inspect.isfunction)
for symbol_name,symbol_info in symbols:
if symbol_info.func_code is code:
scope = module_name + '.'
return scope + code.co_name + resolve_signature(symbol_info)
# Check module's classes.
symbols = inspect.getmembers(module, inspect.isclass)
for symbol_name,symbol_info in symbols:
# Check class' methods.
members = inspect.getmembers(symbol_info, inspect.ismethod)
for method_name,method_info in members:
if method_info.__func__.func_code is code:
scope = module_name + '.' + symbol_name + '.'
return scope + code.co_name + resolve_signature(method_info)
# Default to the thing's name. This usually happens
# when resolving the stack frame for module-level code.
return code.co_name
Frame = namedtuple('Frame', ['call', 'file', 'line', 'help'])
def pretty_stack(traceback=None):
"""Returns a simple stack frame."""
frames = []
if traceback is None:
traceback = sys.exc_info()[2]
while traceback is not None:
frame = traceback.tb_frame
call = resolve_scope(frame.f_globals['__name__'], frame.f_code)
path = frame.f_code.co_filename.replace('\\', '/')
line = frame.f_lineno
docs = frame.f_code.co_consts[0]
if docs == -1:
docs = None
frames.append(Frame(call, path, line, docs))
traceback = traceback.tb_next
return frames
try:
outer(1)
except Exception, error:
frames = pretty_stack()
for frame in frames:
print frame.call
print ' -> "%s", line %d.' % (frame.file, frame.line)
if frame.help:
print frame.help
print
これを実行すると、次のような結果が得られます。
$ python pretty-stack.py
<module>
-> "pretty-stack.py", line 84.
__main__.outer(a, *args, **kwds)
-> "pretty-stack.py", line 14.
__main__.inner(a='qux')
-> "pretty-stack.py", line 11.
Inner function...
nested.external.Foo.bar(self)
-> "C:/Users/acaron/Desktop/nested/external.py", line 3.
これにより、関数のシグネチャとドキュメント文字列も出力されることに注意してください。これは、デバッグ中に役立つ場合があります。
記述子が存在する場合は期待どおりに機能しない可能性がありますが、非常に精巧なユースケースは試していません。動作しないケースが見つかった場合は、お知らせください。パッチを当てます。