Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can two Python argparse objects be combined?

I have an object A which contains parserA - an argparse.ArgumentParser object There is also object B which contains parserB - another argparse.ArgumentParser

Object A contains an instance of object B, however object B's arguments now need to be parsed by the parser in object A (since A is the one being called from the command line with the arguments, not B)

Is there a way to write in Python object A: parserA += B.parserB?

like image 291
MichalD Avatar asked Jun 27 '16 09:06

MichalD


People also ask

What does Nargs do in Argparse?

Using the nargs parameter in add_argument() , you can specify the number (or arbitrary number) of inputs the argument should expect. In this example named sum.py , the --value argument takes in 3 integers and will print the sum.

What is ArgumentParser in Python?

argparse — parse the arguments. Using argparse is how you let the user of your program provide values for variables at runtime. It's a means of communication between the writer of a program and the user. That user might be your future self.

What is Store_true in Python?

The store_true option automatically creates a default value of False. Likewise, store_false will default to True when the command-line argument is not present. The source for this behavior is succinct and clear: http://hg.python.org/cpython/file/2.7/Lib/argparse.py#l861.

What is Argparse ArgumentParser ()?

The argparse module provides a convenient interface to handle command-line arguments. It displays the generic usage of the program, help, and errors. The parse_args() function of the ArgumentParser class parses arguments and adds value as an attribute dest of the object.


2 Answers

You can't use one ArgumentParser inside another. But there is a way around. You need to extract to method code that add arguments to parser. Then you will be able to use them to merge arguments in parser. Also it will be easer to group arguments (related to their parsers). But you must be shore that sets of arguments names do not intersect.

Example:

foo.py:

def add_foo_params( group ):
   group.add_argument('--foo', help='foo help')

if __name__ = "__main__":  
   parser = argparse.ArgumentParser(prog='Foo')

boo.py

def add_boo_params( group ):
   group.add_argument('--boo', help='boo help')

if __name__ = "__main__":  
   parser = argparse.ArgumentParser(prog='Boo')

fooboo.py

   from foo import add_foo_params
   from boo import add_boo_params

   if __name__ = "__main__":  
       parser = argparse.ArgumentParser(prog='FooBoo')
       foo_group = parser.add_argument_group(title="foo params")
       boo_group = parser.add_argument_group(title="boo params")

       add_foo_params( foo_group )
       add_boo_params( boo_group )
like image 24
Arnial Avatar answered Oct 20 '22 01:10

Arnial


argparse was developed around objects. Other than a few constants and utility functions it is all class definitions. The documentation focuses on use rather than that class structure. But it may help to understand a bit of that.

parser = argparse.ArgumentParser(...)

creates a parser object.

arg1 = parser.add_argument(...)

creates an argparse.Action (subclass actually) object and adds it to several parser attributes (lists). Normally we ignore the fact that the method returns this Action object, but occasionally I find it helpful. And when I build a parser in an interactive shell I see a this action.

args = parser.parse_args()

runs another method, and returns an namespace object (class argparse.Namespace).

The group methods and subparsers methods also create and return objects (groups, actions and/or parsers).

The ArgumentParser method takes a parents parameter, where the value is a list of parser objects.

With

parsera = argparse.ArgumentParser(parents=[parserb])

during the creation of parsera, the actions and groups in parserb are copied to parsera. That way, parsera will recognize all the arguments that parserb does. I encourage you to test it.

But there are a few qualifications. The copy is by reference. That is, parsera gets a pointer to each Action defined in parserb. Occasionally that creates problems (I won't get into that now). And one or the other has to have add_help=False. Normally a help action is added to a parser at creation. But if parserb also has a help there will be conflict (a duplication) that has to be resolved.

But parents can't be used if parsera has been created independently of parserb. There's no existing mechanism for adding Actions from parserb. It might possible to make a new parser, with both as parents

parserc = argparse.ArgumentParser(parents=[parsera, parserb])

I could probably write a function that would add arguments from parserb to parsera, borrowing ideas from the method that implements parents. But I'd have to know how conflicts are to be resolved.

Look at the argparse._ActionsContainer._add_container_actions to see how arguments (Actions) are copies from a parent to a parser. Something that may be confusing is that each Action is part of a group (user defined or one of the 2 default groups (seen in the help)) in addition to being in a parser.

Another possibility is to use

[argsA, extrasA] = parserA.parse_known_args()
[argsB, extrasB] = parserB.parse_known_args()  # uses the same sys.argv 
# or
args = parserB.parse_args(extrasA, namespace=argsA)

With this each parser handles the arguments it knows about, and returns the rest in the extras list.

Unless the parsers are designed for this kind of integration, there will be rough edges with this kind of integration. It may be easier to deal with those conficts with Arnial's approach, which is to put the shared argument definitions in your own methods. Others like to put the argument parameters in some sort of database (list, dictionary, etc), and build the parser from that. You can wrap parser creation in as many layers of boilerplate as you find convenient.

like image 102
hpaulj Avatar answered Oct 20 '22 01:10

hpaulj