如何在调用函数时打印它们?

Posted

技术标签:

【中文标题】如何在调用函数时打印它们?【英文标题】:How do I print functions as they are called? 【发布时间】:2012-01-09 01:34:52 【问题描述】:

在调试 Python 脚本时,我真的很想知道我的整个程序的整个调用堆栈。一个理想的情况是,如果有一个用于 python 的命令行标志,它会导致 Python 在调用它们时打印所有函数名称(我检查了 man Python2.7,但没有找到任何此类)。

由于此脚本中的函数数量众多,如果可能,我不希望在每个函数和/或类的开头添加打印语句。

中间的解决方案是使用 PyDev 的调试器,放置几个断点并检查调用堆栈中的给定点在我的程序中,所以我将暂时使用这种方法。

如果存在这样的方法,我仍然希望看到在程序的整个生命周期中调用的所有函数的完整列表。

【问题讨论】:

分析器会告诉你所有调用的函数,例如docs.python.org/library/profile.html 但不完全符合您的要求 - 这是否足够? 【参考方案1】:

您可以使用跟踪函数来做到这一点(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.setprofile(tracefunc)

main()   # or whatever kicks off your script

请注意,函数的代码对象通常与关联函数具有相同的名称,但并非总是如此,因为函数可以动态创建。不幸的是,Python 不跟踪堆栈上的函数对象(我有时幻想着为此提交一个补丁)。不过,在大多数情况下,这肯定“足够好”了。

如果这成为一个问题,您可以从源代码中提取“真正的”函数名——Python 确实会跟踪文件名和行号——或者让垃圾收集器找出哪个函数对象引用了代码对象。可能有多个函数共享代码对象,但它们的任何名称都可能足够好。

四年后再次回顾这一点,我应该提一下,在 Python 2.6 及更高版本中,使用 sys.setprofile() 而不是 sys.settrace() 可以获得更好的性能。可以使用相同的跟踪功能;只是profile函数只有在进入或退出函数时才会被调用,所以函数里面的东西是全速执行的。

【讨论】:

当然,越多越好:-) 这太棒了。我最终将os.path.basename(frame.f_code.co_filename) 添加到此跟踪函数中以打印包含所调用函数的文件。 有什么快速的方法可以减少冗长,只打印对我在代码中定义的函数的调用,而不是所有 Python 的内部函数?至少在 Python 3.4(没有尝试使用 2.7)中,日志中充满了对 notify__getattr__ 等的调用... 您可以查看frame.f_code.co_filename。这应该是包含函数的文件的完整路径。检查路径是否包含Python,后跟lib,如果是,则不要打印任何内容... @Dirk:似乎你可以简单地使用frame.f_code.co_filename 来检查函数是否在你的一个(或多个)源文件中,否则忽略它——而不是检查它是否是 Python 内部的.【参考方案2】:

另一个需要注意的好工具是trace 模块。有 3 种显示函数名称的选项。

例如foo.py:

def foo():
   bar()

def bar():
   print("in bar!")

foo()
    使用-l/--listfuncs列出功能
$ 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: <module>
filename: foo.py, modulename: foo, funcname: bar
filename: foo.py, modulename: foo, funcname: foo
    使用-t/--trace列出正在执行的行
$python -m trace --trace foo.py
 --- modulename: foo, funcname: <module>
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!
    使用-T/--trackcalls 列出什么叫什么
$ python -m trace --trackcalls foo.py
in bar!

calling relationships:

*** /usr/lib/python3.8/trace.py ***
  --> foo.py
    trace.Trace.runctx -> foo.<module>

*** foo.py ***
    foo.<module> -> foo.foo
    foo.foo -> foo.bar

【讨论】:

trace 很有用,但我找不到如何生成 OP 请求的输出:-l 仅显示每个函数一次,-t 显示每一行。【参考方案3】:

我接受了 kindall 的回答并以此为基础。我做了以下模块:

"""traceit.py

Traces the call stack.

Usage:

import sys
import traceit

sys.setprofile(traceit.traceit)
"""

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:->indents()'.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

示例用法

import traceit

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.setprofile() 而不是 sys.settrace() 来提高速度。

【讨论】:

在每个函数打印输出之前我都会得到 15 行破折号 什么是traceit?它没有定义,也不是模块 与其优化代码,解决traceit 未定义这一事实不是更重要吗? @Cyber​​netic:我编辑了答案。我希望现在更清楚了。【参考方案4】:

有几个选项。如果调试器不够用,您可以使用sys.settrace() 设置跟踪功能。这个函数基本上会在每行 Python 代码执行时被调用,但很容易识别函数调用——请参阅链接文档。

您可能还对trace 模块感兴趣,尽管它并不能完全满足您的要求。请务必查看--trackcalls 选项。

【讨论】:

是的,sys.settrace(),与上面@kindall 建议的跟踪功能一起使用就像一个魅力。 :) trace 模块看起来也非常有用......我会记住它以用于将来的调试项目。【参考方案5】:
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

traceback

【讨论】:

这只是一种不太方便且不太灵活的方式来执行 OP 已经使用调试器和断点所做的事情。【参考方案6】:

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

它还提供了一种非常灵活的查询语法,允许指定模块、文件/行号、函数等,这很有帮助,因为默认输出(包括标准库函数调用)可能非常大。

【讨论】:

【参考方案7】:

您可以使用 settrace,如下所述:Tracing python code。使用页面末尾附近的版本。我将该页面的代码粘贴到我的代码中,以查看我的代码运行时究竟执行了哪些行。您还可以进行过滤,以便只看到调用的函数的名称。

【讨论】:

【参考方案8】:

您还可以对要跟踪的特定函数(使用它们的参数)使用装饰器:

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()。

【讨论】:

我喜欢您的回答,但认为可以通过使用 Creating decorator with optional arguments 配方来改进它,这将使其更加“传统”:即 @TraceCalls 而不是 @TraceCalls()。另外——出于同样的原因——我建议将类名全部小写(尽管从技术上讲这不符合 PEP 8 指南)以允许将其用作@tracecalls【参考方案9】:

kindall 答案的变体,只返回包中调用的函数。

def tracefunc(frame, event, arg, indent=[0]):
    package_name = __name__.split('.')[0]

    if event == "call" and (package_name in str(frame)):
        indent[0] += 2
        print("-" * indent[0] + "> call function", frame.f_code.co_name)
    return tracefunc

import sys
sys.settrace(tracefunc)

例如在名为Dog 的包中,这应该只显示在Dog 包中定义的调用函数。

【讨论】:

以上是关于如何在调用函数时打印它们?的主要内容,如果未能解决你的问题,请参考以下文章

PHP - 如何打印函数调用树

在内核函数中调用设备函数时如何测量它们的时间[重复]

在 main 中调用打印地图的函数时的 C++ 问题

调用一个函数来打印一些东西[关闭]

调用内联函数时未定义的引用

为啥在烧瓶中没有调用函数时会有一些打印? [复制]