web-dev-qa-db-ja.com

呼び出された関数をどのように印刷しますか

Pythonスクリプトのデバッグでは、プログラム全体のコールスタック全体を知りたいと思います。理想的な状況は、pythonこれにより、Pythonが呼び出されたときにすべての関数名を出力します(私はman Python2.7、しかしこの種のものは何も見つかりませんでした)。

このスクリプトには多数の関数があるため、可能であれば、各関数やクラスの先頭にprintステートメントを追加しないことをお勧めします。

中間的な解決策は、PyDevのデバッガーを使用して、いくつかのブレークポイントを配置し、プログラム内の特定のポイントの呼び出しスタックをチェックすることです。したがって、当面はこのアプローチを使用します。

そのようなメソッドが存在する場合、プログラムの全期間を通じて呼び出されるすべての関数の完全なリストを見たいと思っています。

59
James

トレース機能を使用してこれを行うことができます(リターンのトレースとニースのインデントを使用するためにこの元のバージョンを改善するためのSpacedmanの小道具):

_def tracefunc(frame, event, arg, indent=[0]):
      if event == "call":
          indent[0] += 2
          print("-" * indent[0] + "> call function", frame.f_code.co_name)
      Elif event == "return":
          print("<" + "-" * indent[0], "exit function", frame.f_code.co_name)
          indent[0] -= 2
      return tracefunc

import sys
sys.settrace(tracefunc)

main()   # or whatever kicks off your script
_

関数は動的に作成できるため、通常、関数のコードオブジェクトには関連する関数と同じ名前が付けられますが、常にではありません。残念ながら、Pythonはスタック上の関数オブジェクトを追跡しません(これに対するパッチを提出することを時々夢想しました)。それでも、ほとんどの場合、これは確かに「十分」です。

これが問題になる場合は、ソースコードから「実際の」関数名を抽出し(Pythonはファイル名と行番号を追跡します)、ガベージコレクターにどの関数オブジェクトがコードオブジェクトを参照しているかを尋ねます。コードオブジェクトを共有する複数の関数が存在する可能性がありますが、それらの名前はどれでも十分です。

4年後の再訪に戻って、Python 2.6以降では、以下を使用することでより良いパフォーマンスを得ることができます。 sys.setprofile()ではなくsys.settrace()。同じトレース関数を使用できます;関数が開始または終了したときにのみプロファイル関数が呼び出されるため、関数の内部が完全に実行されます速度。

92
kindall

知っておくべきもう1つの優れたツールは、 trace モジュールです。

$ cat foo.py
def foo():
   bar()

def bar():
   print "in bar!"

foo()

$ python -m trace --listfuncs foo.py
in bar!

functions called:
filename: /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/trace.py, modulename: trace, funcname: _unsettrace
filename: foo.py, modulename: foo, funcname: 
filename: foo.py, modulename: foo, funcname: bar
filename: foo.py, modulename: foo, funcname: foo

$python -m trace --trace foo.py
 --- modulename: foo, funcname: 
foo.py(1): def foo():
foo.py(4): def bar():
foo.py(7): foo()
 --- modulename: foo, funcname: foo
foo.py(2):    bar()
 --- modulename: foo, funcname: bar
foo.py(5):    print "in bar!"
in bar!
 --- modulename: trace, funcname: _unsettrace
trace.py(80):         sys.settrace(None)

12
David Wolever

いくつかのオプションがあります。デバッガーでは不十分な場合は、 sys.settrace() を使用してtrace functionを設定できます。この関数は、実行されるPythonコードのすべての行で基本的に呼び出されますが、関数呼び出しを簡単に識別できます。リンクされたドキュメントを参照してください。

trace モジュールにも興味があるかもしれませんが、それはあなたが要求したことを正確には行いません。 --trackcallsオプション。

7
Sven Marnach
import traceback
def foo():
    traceback.print_stack()
def bar():
    foo()
def car():
    bar():

car()
File "<string>", line 1, in <module>
File "C:\Python27\lib\idlelib\run.py", line 97, in main
  ret = method(*args, **kwargs)
File "C:\Python27\lib\idlelib\run.py", line 298, in runcode
    exec code in self.locals
File "<pyshell#494>", line 1, in <module>
File "<pyshell#493>", line 2, in car
File "<pyshell#490>", line 2, in bar
File "<pyshell#486>", line 2, in foo

トレースバック

5
Abhijit

Kindallの答えを取り、それに基づいて構築しました。

import sys


WHITE_LIST = ['trade']      # Look for these words in the file path.
EXCLUSIONS = ['<']          # Ignore <listcomp>, etc. in the function name.


def tracefunc(frame, event, arg):

    if event == "call":
        tracefunc.stack_level += 1

        unique_id = frame.f_code.co_filename+str(frame.f_lineno)
        if unique_id in tracefunc.memorized:
            return

        # Part of filename MUST be in white list.
        if any(x in frame.f_code.co_filename for x in WHITE_LIST) \
            and \
          not any(x in frame.f_code.co_name for x in EXCLUSIONS):

            if 'self' in frame.f_locals:
                class_name = frame.f_locals['self'].__class__.__name__
                func_name = class_name + '.' + frame.f_code.co_name
            else:
                func_name = frame.f_code.co_name

            func_name = '{name:->{indent}s}()'.format(
                    indent=tracefunc.stack_level*2, name=func_name)
            txt = '{: <40} # {}, {}'.format(
                    func_name, frame.f_code.co_filename, frame.f_lineno)
            print(txt)

            tracefunc.memorized.add(unique_id)

    Elif event == "return":
        tracefunc.stack_level -= 1


tracefunc.memorized = set()
tracefunc.stack_level = 0


sys.setprofile(traceit.tracefunc)

サンプル出力:

API.getFills()                           # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 331
API._get_req_id()                        # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1053
API._wait_till_done()                    # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1026
---API.execDetails()                     # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1187
-------Fill.__init__()                   # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 256
--------Price.__init__()                 # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 237
-deserialize_order_ref()                 # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 644
--------------------Port()               # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 647
API.commissionReport()                   # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1118

特徴:

  • 無視Python言語内部関数。
  • 繰り返される関数呼び出しを無視します(オプション)。
  • 速度のためにsys.settrace()の代わりにsys.setprofile()を使用します。
2
ChaimG

hunter ツールはこれを正確に行います。たとえば、次の場合:

test.py

def foo(x):
    print(f'foo({x})')

def bar(x):
    foo(x)

bar()

出力は次のようになります。

$ PYTHONHUNTER='module="__main__"' python test.py
                                 test.py:1     call      => <module>()
                                 test.py:1     line         def foo(x):
                                 test.py:4     line         def bar(x):
                                 test.py:7     line         bar('abc')
                                 test.py:4     call         => bar(x='abc')
                                 test.py:5     line            foo(x)
                                 test.py:1     call            => foo(x='abc')
                                 test.py:2     line               print(f'foo({x})')
foo(abc)
                                 test.py:2     return          <= foo: None
                                 test.py:5     return       <= bar: None
                                 test.py:7     return    <= <module>: None

また、モジュール、ファイル/ lineno、関数などを指定できる非常に柔軟なクエリ構文を提供します。これは、デフォルトの出力(標準ライブラリ関数呼び出しを含む)がかなり大きくなる可能性があるためです。

2
Chris Hunt

ここに概説されているように、settraceを使用できます。 Tracing python code 。ページの終わり近くにあるバージョンを使用します。コードが実行されているときに実行されている行を正確に確認し、呼び出された関数の名前のみを表示するようにフィルタリングすることもできます。

2
jeorgen

また、トレースする特定の関数(および引数)にデコレータを使用することもできます。

import sys
from functools import wraps

class TraceCalls(object):
    """ Use as a decorator on functions that should be traced. Several
        functions can be decorated - they will all be indented according
        to their call depth.
    """
    def __init__(self, stream=sys.stdout, indent_step=2, show_ret=False):
        self.stream = stream
        self.indent_step = indent_step
        self.show_ret = show_ret

        # This is a class attribute since we want to share the indentation
        # level between different traced functions, in case they call
        # each other.
        TraceCalls.cur_indent = 0

    def __call__(self, fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            indent = ' ' * TraceCalls.cur_indent
            argstr = ', '.join(
                [repr(a) for a in args] +
                ["%s=%s" % (a, repr(b)) for a, b in kwargs.items()])
            self.stream.write('%s%s(%s)\n' % (indent, fn.__name__, argstr))

            TraceCalls.cur_indent += self.indent_step
            ret = fn(*args, **kwargs)
            TraceCalls.cur_indent -= self.indent_step

            if self.show_ret:
                self.stream.write('%s--> %s\n' % (indent, ret))
            return ret
        return wrapper

このファイルをインポートし、トレースする関数/メソッドの前に@TraceCalls()を追加するだけです。

1
Vincent Fenet