Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional argument for each positional argument

Using argparse (or something else?) I would like each positional argument to have an optional argument with default value.

The arguments would be as so:

script.py arg1 arg2 -o 1 arg3 -o 2 arg4 arg5

and I want it to parse this into something usable, like a list of the positional arguments and a list of the optional arguments with defaults filled in. e.g. if the default for the optional is 0 in the example above:

positional = [arg1, arg2, arg3, arg4, arg5]
optional = [0, 1, 2, 0, 0]

in other words, parser.add_argument('-o', action='append') is not what I want because I lose the positional argument, each optional argument is associated to.

like image 231
bkanuka Avatar asked Nov 03 '22 06:11

bkanuka


1 Answers

Here's a simple hack that I put together that might be a reasonable place to start:

import argparse

class PositionalAction(argparse.Action):
    def __call__(self,parser,namespace,values,option_string=None):
        lst = getattr(namespace,self.dest)
        lst.append(values)
        parser.last_positional_values = lst
        all_positional = getattr(namespace,'all_positional',[])
        all_positional.append(lst)
        namespace.all_positional = all_positional

class AssociateAction(argparse.Action):
    def __call__(self,parser,namespace,values,option_string=None):
        try:
            parser.last_positional_values.append(values)
        except AttributeError:
            pass

parser = argparse.ArgumentParser()
parser.add_argument('-o',action=AssociateAction,dest=argparse.SUPPRESS)
junk,unknown = parser.parse_known_args()

for i,_ in enumerate(unknown):
    parser.add_argument('arg%d'%i,action=PositionalAction,default=[])

print parser.parse_args()

And here it is in action:

temp $ python test1.py foo -o 1 bar -o 2 baz qux -o 4
Namespace(all_positional=[['foo', '1'], ['bar', '2'], ['baz'], ['qux', '4']], arg0=['foo', '1'], arg1=['bar', '2'], arg2=['baz'], arg3=['qux', '4'])

This problem has a few challenges. First, You want to accept an arbitrary number of positional arguments -- argparse doesn't like that. argparse wants to know up front what to expect. The solution is to build a parser and parse the commandline, but to tell argparse to only parse the known arguments only (in this case, the non-positional -o arguments are all parsed silently but the "positional" arguments aren't parsed.). parse_known_args is perfect for this as it returns a tuple in the form (namespace_of_parsed_stuff, uknown_args). So now we know the unknown arguments -- We just need to add a positional argument to the parser for each one to make parse_args happy.

Now, what are the custom actions actually doing? When a positional argument is found (on the second pass), we get the default (which is a list) and add the value to that list (hereafter I'll call it the "value" list). We then modify the parser with a reference to the "value" list. We also get the "all_positional" list from the namespace. If it doesn't have that attribute, we just get an empty list. We add the "value" list to the "all_positional" list and put it back on the namespace.

Now, when we hit a -o flag, we look at the parser to get the "value" list and we add the additional value to that list. We could do the same thing without touching the parser at all ... (we could look at namespace.all_positional[-1] -- It's the same list as parser.last_positional_values).

like image 122
mgilson Avatar answered Nov 15 '22 05:11

mgilson