Programming
python argparse
Updated Sun, 31 Jul 2022 12:37:01 GMT

Multiple positional arguments with Python and argparse


I'm trying to use argparse to parse the command line arguments for a program I'm working on. Essentially, I need to support multiple positional arguments spread within the optional arguments, but cannot get argparse to work in this situation. In the actual program, I'm using a custom action (I need to store a snapshot of the namespace each time a positional argument is found), but the problem I'm having can be replicated with the append action:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-a', action='store_true')
>>> parser.add_argument('-b', action='store_true')
>>> parser.add_argument('input', action='append')
>>> parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
usage: ipython [-h] [-a] [-b] input
ipython: error: unrecognized arguments: filetwo filethree

I'd like this to result in the namespace (a=True, b=True, input=['fileone', 'filetwo', 'filethree']), but cannot see how to do this - if indeed it can. I can't see anything in the docs or Google which says one way or the other if this is possible, although its quite possible (likely?) I've overlooked something. Does anyone have any suggestions?




Solution

srgerg was right about the definition of positional arguments. In order to get the result you want, You have to accept them as optional arguments, and modify the resulted namespace according to your need.

You can use a custom action:

class MyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        # Set optional arguments to True or False
        if option_string:
            attr = True if values else False
            setattr(namespace, self.dest, attr)
        # Modify value of "input" in the namespace
        if hasattr(namespace, 'input'):
            current_values = getattr(namespace, 'input')
            try:
                current_values.extend(values)
            except AttributeError:
                current_values = values
            finally:
                setattr(namespace, 'input', current_values)
        else:
            setattr(namespace, 'input', values)
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', action=MyAction)
parser.add_argument('-b', nargs='+', action=MyAction)
parser.add_argument('input', nargs='+', action=MyAction)

And this is what you get:

>>> parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])

Or you can modify the resulted namespace like this:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-a', nargs='+')
>>> parser.add_argument('-b', nargs='+')
>>> parser.add_argument('input', nargs='+')
>>> result = parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
>>> inputs = []
>>> inputs.extend(result.a)
>>> inputs.extend(result.b)
>>> inputs.extend(result.input)
>>> modified = argparse.Namespace(
        a = result.a != [],
        b = result.b != [],
        input = inputs)

And this is what you get:

>>> modified
Namespace(a=True, b=True, input=['filetwo', 'filethree', 'fileone'])

However, both method result in less readable and less maintainable code. Maybe it's better to change the program logic and do it in a different way.





Comments (1)

  • +0 – Thanks for the examples. I'd like to be able to support multiple positional arguments, but at the same time I prefer not to have to maintain workarounds like this. Looks like I have some decisions to make... — Mar 22, 2011 at 23:13