4

Python のスタック トレースは、ファイル パスを示します。代わりに完全修飾関数名を表示する方法はありますか?

例:

class Foo(object):
    def bar(self):
        raise Exception, "Some error."

def inner():
    return Foo().bar()

def outer():
    return inner()

出力を次のようにしたいと思います。

In __main__.Foo.bar ("scratchpad.py", line 3)
   __main__.inner ("scratchpad.py", line 6)
   __main__.outer ("scratchpad.py", line 9)
Exception: Some error.

何か変更があれば、私は Python 2.7 を使用しています。


これが私がこれまでに持っているものです:

import sys

class Foo(object):
    def bar(self):
        raise Exception, "Dummy value."

def inner():
    "Inner function..."
    return Foo().bar()

def outer():
    return inner()


try:
    outer()
except Exception, error:
    traceback = sys.exc_info()[2]
    while traceback is not None:
        frame = traceback.tb_frame
        print 'Name', frame.f_globals['__name__']+'.'+frame.f_code.co_name
        docs = frame.f_code.co_consts[0]
        if docs and docs != -1: # docs can be None or -1.
            print 'Docs "%s"' % docs
        print 'Args', frame.f_code.co_argcount
        print 'File "%s", line %d' % (frame.f_code.co_filename, frame.f_lineno)
        print
        traceback = traceback.tb_next

実行すると、印刷されます

$ python pretty-stack.py
Name __main__.<module>
Args 0
File "pretty-stack.py", line 28

Name __main__.outer
Args 0
File "pretty-stack.py", line 14

Name __main__.inner
Docs "Inner function..."
Args 0
File "pretty-stack.py", line 11

Name __main__.bar
Args 1
File "pretty-stack.py", line 7

ほぼ完成ですが、重要なユースケースに問題があります。たとえば、 のクラス名Fooを取得できませんFoo.bar()

4

1 に答える 1

4

「コード オブジェクト」のみにアクセスできるため、トレースバックからシンボルに直接アクセスする方法はありません。また、コード オブジェクトに関する 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.

これにより、関数のシグネチャとドキュメント文字列も出力されることに注意してください。これは、デバッグ中に役立つ場合があります。

記述子が存在する場合は期待どおりに機能しない可能性がありますが、非常に精巧なユースケースは試していません。動作しないケースが見つかった場合は、お知らせください。パッチを当てます。

于 2013-02-11T21:39:37.850 に答える