Question

When developing a Python package, it's very convenient to use the -m option to run modules inside the package as scripts for quick testing. For example, for somepackage with module somemodule.py inside it, invoking

python -m somepackage.somemodule

from the directory where somepackage resides will run somemodule.py as though the submodule were __main__. Using this calling syntax is especially important if the package is using explicit relative imports as described here.

Similarly, it is also convenient to use the -m option to debug a script, as in

python -m pdb somescript.py

Is there any way to do both at the same time? That is, can I call a module as though it were a script and simultaneously launch into the debugger? I realize I can go into the code itself and insert import pdb; pdb.set_trace() where I want to break, but I'm trying to avoid that.

Was it helpful?

Solution 2

After experimenting with this for quite some time, it turns out that this approach actually works:

python -c "import runpy; import pdb; pdb.runcall(runpy.run_module, 'somepackage.somemodule', run_name='__main__')"

For some reason, the use of pdb.runcall over pdb.run is important.

OTHER TIPS

There are efforts underway to solve this in Python itself. Looks like with Python 3.7, you can do:

python -m pdb -m somepackage.somemodule

And I've provided a backport for older Python versions (2.7+):

pip install backports.pdb
python -m backports.pdb -m somepackage.somemodule

Building on @jed's answer, I built this module:

import pdb
import runpy
import sys


def main():
    module = sys.argv[1]
    sys.argv[1:] = sys.argv[2:]
    pdb.runcall(runpy.run_module, module, run_name='__main__')


__name__ == '__main__' and main()

Put that module as mpdb.py anywhere in your Python Path (current directory works), then you may invoke:

python -m mpdb somepackage.somemodule even with args

Here's another option that also works with command line arguments.

It's generally a good idea to wrap your script's logic in a main function. You can then have main take in an optional list of arguments to override sys.argv. Here's an example called argdemo.py:

def main(cmd_line_args=None):
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("number", help="a number", type=int)

    # allow cmd_line_args to override sys.argv
    if cmd_line_args is None:
        args = parser.parse_args()
    else:
        args = parser.parse_args(cmd_line_args)

    print("The number is {}".format(args.number))

if __name__ == '__main__':
    main()

This module can be run as usual:

$ python -m argdemo 2
> The number is 2

Or it can be run with pdb by calling main() directly:

$ python -c "import pdb; import argdemo; pdb.runcall(argdemo.main, ['2'])"
(Pdb) continue
> The number is 2

(Notice that cmd_line_args has to be a list of strings just like argv would be).

As an added bonus, when your module has an import-able main function, you can write unit tests for it in the same way =)

This worked for me (debug python module as a script with -m option)

I created a scratch

import runpy

if __name__ == '__main__':
    runpy.run_module('somepackage.somemodule', run_name="__main__", alter_sys=True)

idea taken from: Intellij/Pycharm can't debug Python modules

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top