Python 如何打印完整堆栈,包括使用的魔法方法(dunder methods)?

Python how to print full stack, including magic methods (dunder methods) used?

我正在尝试调试 Python 内置 class。我的调试使我进入了魔术方法(又名 dunder 方法)的领域。

我正在尝试找出调用了哪些 dunder 方法(如果有的话)。通常我会做这样的事情:

import sys
import traceback

# This would be located where the I'm currently debugging
traceback.print_stack(file=sys.stdout)

但是,traceback.print_stack 没有给我详细程度的打印在其附近使用的 dunder 方法区域。

有什么方法可以非常详细地打印出代码块中实际发生的事情吗?


示例代码

#!/usr/bin/env python3.6

import sys
import traceback
from enum import Enum


class TestEnum(Enum):
    """Test enum."""

    A = "A"


def main():
    for enum_member in TestEnum:
        traceback.print_stack(file=sys.stdout)
        print(f"enum member = {enum_member}.")


if __name__ == "__main__":
    main()

我希望上面的示例代码能够打印出任何使用的双打方法(例如:__iter__)。

目前它打印出调用 traceback.print_stack:

的路径
/path/to/venv/bin/python /path/to/file.py
  File "/path/to/file.py", line 56, in <module>
    main()
  File "/path/to/file.py", line 51, in main
    traceback.print_stack(file=sys.stdout)
enum member = TestEnum.A.

P.S。我对进入 dis.dis.

给出的字节码级别不感兴趣

我认为,通过堆栈跟踪,您看错了地方。当你从一个地方调用 print_stack 时,只有当来自 dunder 方法时才会执行,这个方法很好地包含在输出中。

我试过这段代码来验证:

import sys
import traceback
from enum import Enum


class TestEnum(Enum):
    """Test enum."""

    A = "A"


class MyIter:

    def __init__(self):
        self.i = 0

    def __next__(self):
        self.i += 1
        if self.i <= 1:
            traceback.print_stack(file=sys.stdout)
            return TestEnum.A
        raise StopIteration

    def __iter__(self):
        return self


def main():
    for enum_member in MyIter():
        print(f"enum member = {enum_member}.")


if __name__ == "__main__":
    main()

堆栈跟踪的最后一行打印为

File "/home/lydia/playground/demo.py", line 21, in __next__
traceback.print_stack(file=sys.stdout)

在您的原始代码中,您在所有 dunder 方法都已返回时获取堆栈跟踪。因此它们已从堆栈中移除。

所以我想,您想看看调用图。我知道 IntelliJ / PyCharm 至少在付费版本中可以很好地做到这一点。

您可能还想尝试其他工具。 pycallgraph 你觉得如何?

更新:

Python 实际上很容易转储所有函数调用的简单列表。

基本上你需要做的就是

import sys
sys.setprofile(tracefunc)

根据需要写tracefunc。在这个 SO 问题中找到一个工作示例:How do I print functions as they are called

警告: 我需要从外部启动脚本 shell。使用我的 IDE 中的播放按钮启动它意味着脚本永远不会终止,但会写入越来越多的行。我假设它与我的 IDE.

所做的内部分析相冲突

sys.setprofile的官方文档:https://docs.python.org/3/library/sys.html#sys.setprofile

以及 Python 中关于追踪的随机教程:https://pymotw.com/2/sys/tracing.html

但是请注意,根据我的经验,您可以通过使用普通的调试器获得对问题 "who is calling whom?" 或 "where does this value even come from?" 的最佳见解。

我还对该主题进行了一些研究,因为@LydiaVanDyke 的回答中的信息推动了更好的搜索。

打印整个调用堆栈

正如@LydiaVanDyke 指出的那样,IDE 调试器是一种非常好的方法。我使用 PyCharm,发现这是我最喜欢的解决方案,因为可以:

  • 跟随函数调用 + 代码中的确切行号
  • 阅读调用代码,更好地理解输入
  • 跳过那些不关心调查的电话

另一种方式是Python的标准库trace。它提供命令行和可嵌入的方法来打印整个调用堆栈。

还有一个是 Python 的内置调试器模块 pdb。这(通过 pdb.set_trace() 调用)真的改变了我的游戏。

Profiler 输出的可视化

gprof2dot 是另一个有用的分析器可视化工具。

查找源代码

由于我的 IDE 存根文件 (PyCharm),我的另一个问题是实际上没有看到真正的源代码。

How to retrieve source code of Python functions详解两种实际打印源码的方法


使用所有这些工具,让人感觉充满力量!