【问题标题】:Python how to print full stack, including magic methods (dunder methods) used?Python如何打印全栈,包括使用的魔术方法(dunder methods)?
【发布时间】:2020-07-24 15:51:58
【问题描述】:

我正在尝试调试 Python 内置类。我的调试使我进入了魔术方法(又名 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()

我想要上面的示例代码打印出任何使用的 dunder 方法(例如:__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.

附:我对去dis.dis给出的字节码级别不感兴趣。

【问题讨论】:

    标签: python debugging stack traceback magic-methods


    【解决方案1】:

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

    打印整个调用堆栈

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

    • 按照函数调用 + 代码中的确切行号
    • 阅读调用周围的代码,更好地理解打字
    • 跳过不想调查的电话

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

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

    Profiler 输出的可视化

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

    查找源代码

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

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


    有了所有这些工具,人们会感到非常有能力!

    【讨论】:

      【解决方案2】:

      我认为,使用堆栈跟踪,您看错了地方。当您从某个地方调用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

      但是请注意,根据我的经验,您可以对“谁在呼叫谁?”等问题获得最佳洞察。或者“这个价值是从哪里来的?”通过使用普通的调试器。

      【讨论】:

      • 感谢@LydiaVanDyke 的精彩回答!我尝试了pycallgraph,我认为这很有用。我不知道整个分析世界。根据这个答案:stackoverflow.com/q/582336/11163122 我看到还有其他资源。
      • 您能否推荐任何工具,以基于文本的方式打印完整的堆栈历史记录,而不是 pycallgraph 的可视方式?
      • 我怀疑我能否找到您发布的 SO 线程中未提及的任何内容。 python -m cProfile 将是我的下一个 - 这在第二个答案中显示(获得的票数比最高票数更多)。官方文档似乎没有链接:docs.python.org/3/library/profile.html
      • 还有一点……我更新了回复。
      • 非常感谢@LydiaVanDyke!这是很多很棒的信息 :) 你的搜索技巧比我的好得多!
      猜你喜欢
      • 1970-01-01
      • 2016-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-29
      • 2015-08-08
      • 2020-07-17
      相关资源
      最近更新 更多