Python, как печатать полный стек, включая используемые магические методы (дандер-методы)?

Я пытаюсь отладить встроенный класс Python. Моя отладка привела меня в область магических методов (методов Дандера).

Я пытаюсь выяснить, какие методы 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.

P.S. Меня не интересует переход на уровень байт-кода, заданный dis.dis.


person Intrastellar Explorer    schedule 11.04.2020    source источник


Ответы (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: Как мне печатать функции как они называются

Внимание! Мне нужно было запустить скрипт из внешней оболочки. Запуск его с помощью кнопки воспроизведения в моей среде IDE означал, что скрипт никогда не завершится, а будет писать все больше и больше строк. Я предполагаю, что это противоречит внутреннему профилированию, выполненному моей IDE.

Официальная документация sys.setprofile: https://docs.python.org/3/library/sys.html#sys.setprofile

И случайное руководство по трассировке в Python: https://pymotw.com/2/sys/tracing.html

Обратите внимание, однако, что, по моему опыту, вы можете получить лучшее представление о вопросах «кто кому звонит?» или "откуда вообще берется это значение?" с помощью старого доброго отладчика.

person Lydia van Dyke    schedule 12.04.2020
comment
Спасибо @LydiaVanDyke за отличный ответ! Я попробовал pycallgraph и думаю, что это весьма полезно. Я не знал обо всем мире профилирования. Согласно этому ответу: stackoverflow.com/q/582336/11163122 я вижу, что есть и другие ресурсы. - person Intrastellar Explorer; 13.04.2020
comment
Можете ли вы предложить какие-либо инструменты, которые просто печатают полную историю стека в текстовом виде, в отличие от визуального способа pycallgraph? - person Intrastellar Explorer; 13.04.2020
comment
Я сомневаюсь, что смогу найти что-то, что еще не упомянуто в теме SO, которую вы разместили. python -m cProfile был бы моим следующим - и это показано во втором ответе (получил больше голосов, чем лучший). Официальная документация, похоже, не связана: docs.python.org/3/library/ профиль.html - person Lydia van Dyke; 13.04.2020
comment
Ну и еще немного... Я обновил ответ. - person Lydia van Dyke; 13.04.2020
comment
Большое спасибо @LydiaVanDyke!! Это много полезной информации :) ваши навыки поиска намного лучше моих! - person Intrastellar Explorer; 13.04.2020

Я также провел некоторое исследование по этому вопросу, так как информация в ответе @LydiaVanDyke способствовала улучшению поиска.

Печать всего стека вызовов

Как отмечает @LydiaVanDyke, отладчик IDE — действительно отличный способ. Я использую PyCharm и обнаружил, что это мое любимое решение, потому что можно:

  • Следуйте вызовам функций + точные номера строк в коде
  • Читайте код вокруг вызовов, лучше понимая ввод
  • Пропускайте звонки, которые никто не хочет расследовать

Другой способ — стандартная библиотека Python trace. Он предлагает как командную строку, так и встраиваемые методы для печати всего стека вызовов.

И еще один — встроенный в Python модуль отладчика, pdb. Это (вызывается через pdb.set_trace()) действительно изменило игру для меня.

Визуализация выходных данных профилировщика

gprof2dot — еще один полезный инструмент визуализации профилировщика.

Поиск исходного кода

Одна из моих других проблем заключалась в том, что я не видел настоящий исходный код из-за файлов-заглушек моей IDE (PyCharm).

Как получить исходный код функций Python подробно описывает два метода фактической печати исходного кода


Со всеми этими инструментами чувствуешь себя вполне уверенно!

person Intrastellar Explorer    schedule 13.04.2020