Search code examples
python-2.7functionargparsesubparsers

Argparse using function from another file within that file


I have two classes:

OptionParser
Application

stored in separate files (option_parser.py and application.py). The former defines how command line input should be handled. The latter imports the former, reads the user input, and carries on with the request.

How do I call a method defined in application.py's Application without importing it inside option_parser.py (as that would lead to an infinite loop, since application.py imports OptionParser from option_parser.py). If I use set_defaults(func=method_name), then I am told that global function method_name is not defined in that scope.

I already have a workaround for this, but it just doesn't seem right:

if self.options.action == 'tags':
    self.list_tags()
elif self.options.action == 'branches':
    self.list_branches()

I've tried to find the answer in the official documentation, Google, and on SO - to no avail.


Solution

  • I assume you are using set_defaults(func=method_name) with a subparser as illustrated in the argparse docs? That's an easy way of invoking a function. It takes advantage of flexibility built into defining defaults and setting attributes in the args namespace. But there's no code devoted to this feature.

    Associating functions with actions like this is typically the easiest way to handle the different actions for each of your subparsers. However, if it is necessary to check the name of the subparser that was invoked, the dest keyword argument to the add_subparsers() call will work:

    That 2nd sentence is giving you permission to use

    if self.options.action == 'tags'
    

    In your case set_defaults is not the easiest way.

    argparse is primarily a means of finding out what your user wants. It isn't meant to be a dispatcher. Usually that's the responsibility of your code.

    My impression from other argparse questions is that most of the time programmers don't use this set_defaults option. A search of SO for [argparse] set_default only turns up 37 posts (answers or questions) (compared to 1.5K questions for just the argparse tag).


    Here's a working script that implements my idea of passing set_defaults values to an parser setup function. It's one file, but the parser part could just as well be imported.

    import argparse
    
    def make_parser(defaults):
        parser = argparse.ArgumentParser()
        sp = parser.add_subparsers(dest='cmdstr')
        sp.required = True
        for key in defaults:
            spp = sp.add_parser(key)
            spp.set_defaults(cmd=defaults[key])
        return parser
    
    def foo(args):
        print('foo')
    
    def bar(args):
        print('bar')
    
    if __name__=='__main__':
        dd = {'cmd1': foo, 'cmd2': bar}
        parser = make_parser(dd)
        args = parser.parse_args()
        print(args)
        args.cmd(args)
    

    Sample calls:

    0930:~/mypy$ python3 stack42897689.py cmd1
    Namespace(cmd=<function foo at 0xb70c2dac>, cmdstr='cmd1')
    foo
    0931:~/mypy$ python3 stack42897689.py cmd2
    Namespace(cmd=<function bar at 0xb7145614>, cmdstr='cmd2')
    bar
    

    With this dd dictionary, I could just as easily have done the dispatching with:

    dd[args.cmdstr](None)
    

    The basic goal is to pair up a commandline string with a function call. set_defaults is one way to do within argparse. But dictionary like dd is also widely used in Python code. And so are if/else statements.