Intrastellar Explorer
Intrastellar Explorer

Reputation: 2471

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

I am trying to debug a Python built-in class. My debugging has brought me into the realm of magic methods (aka dunder methods).

I am trying to figure out which dunder methods are called, if any. Normally I would do something like this:

import sys
import traceback

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

However, traceback.print_stack does not give me the level of detail of printing what dunder methods area used in its vicinity.

Is there some way I can print out, in a very verbose manner, what is actually happening inside a block of code?


Sample Code

#!/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()

I would like the above sample code to print out any dunder methods used (ex: __iter__).

Currently it prints out the path to the call to 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. I'm not interested in going to the byte code level given by dis.dis.

Upvotes: 0

Views: 791

Answers (2)

Intrastellar Explorer
Intrastellar Explorer

Reputation: 2471

I also did some research on the subject matter, as information in @LydiaVanDyke's answer fueled better searches.

Printing Entire Call Stack

As @LydiaVanDyke points out, an IDE debugger is a really great way. I use PyCharm, and found that was my favorite solution, because one can:

  • Follow function calls + exact line numbers in the code
  • Read code around the calls, better understanding typing
  • Skip over calls one doesn't care to investigate

Another way is Python's standard library's trace. It offers both command line and embeddable methods for printing the entire call stack.

And yet another one is Python's built-in debugger module, pdb. This (invoked via pdb.set_trace()) really changed the game for me.

Visualization of Profiler Output

gprof2dot is another useful profiler visualization tool.

Finding Source Code

One of my other problems was not actually seeing the real source code, due to my IDE's stub files (PyCharm).

How to retrieve source code of Python functions details two methods of actually printing source code


With all this tooling, one feels quite empowered!

Upvotes: 0

Lydia van Dyke
Lydia van Dyke

Reputation: 2526

I think, with the stacktrace you are looking at the wrong place. When you call print_stack from a place, that is executed only when coming from a dunder method, this method is very well included in the output.

I tried this code to verify:

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

The last line of the stack trace is printed as

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

In your original code, you are getting the stack trace at a time when all dunder methods have already returned. Thus they have been removed from the stack.

So I think, you want to have a look at a call graph instead. I know that IntelliJ / PyCharm can do this nicely at least in the paid editions.

There are other tools that you may want to try. How does pycallgraph look to you?

Update:

Python makes it actually pretty easy to dump a plain list of all the function calls.

Basically all you need to do is

import sys
sys.setprofile(tracefunc)

Write the tracefunc depending on your needs. Find a working example at this SO question: How do I print functions as they are called

Warning: I needed to start the script from an external shell. Starting it by using the play button in my IDE meant that the script would never terminate but write more and more lines. I assume it collides with the internal profiling done by my IDE.

The official documentation of sys.setprofile: https://docs.python.org/3/library/sys.html#sys.setprofile

And a random tutorial about tracing in Python: https://pymotw.com/2/sys/tracing.html

Note however, that by my experience you can get the best insights into the questions "who is calling whom?" or "where does this value even come from?" by using a plain-old debugger.

Upvotes: 2

Related Questions