Skip to content
Advertisement

In Python argparse, is it possible to have paired –no-something/–something arguments?

I’m writing a program in which I would like to have arguments like this:

--[no-]foo   Do (or do not) foo. Default is do.

Is there a way to get argparse to do this for me?

Advertisement

Answer

v3.9 has added an action class that does this. From the docs (near the end of the action section)

The BooleanOptionalAction is available in argparse and adds support for boolean actions such as --foo and --no-foo:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', action=argparse.BooleanOptionalAction)
>>> parser.parse_args(['--no-foo'])
Namespace(foo=False)

To explore @wim’s comment about not being mutually_exclusive.

In [37]: >>> parser = argparse.ArgumentParser()
    ...: >>> parser.add_argument('--foo', action=argparse.BooleanOptionalAction)
Out[37]: BooleanOptionalAction(option_strings=['--foo', '--no-foo'], dest='foo', nargs=0, const=None, default=None, type=None, choices=None, help=None, metavar=None)

The last line shows that the add_argument created a BooleanOptionalAction Action class.

With various inputs:

In [38]: parser.parse_args('--foo'.split())
Out[38]: Namespace(foo=True)

In [39]: parser.parse_args('--no-foo'.split())
Out[39]: Namespace(foo=False)

In [40]: parser.parse_args([])
Out[40]: Namespace(foo=None)

In [41]: parser.parse_args('--no-foo --foo'.split())
Out[41]: Namespace(foo=True)

So you can supply both flags, with the last taking effect, over writing anything produced by the previous. It’s as though we defined two Actions, with the same dest, but different True/False const.

The key is that it defined two flag strings:

option_strings=['--foo', '--no-foo']

Part of the code for this new class:

class BooleanOptionalAction(Action):
    def __init__(self,
                 option_strings,
                 dest,
                 ...):

        _option_strings = []
        for option_string in option_strings:
            _option_strings.append(option_string)

            if option_string.startswith('--'):
                option_string = '--no-' + option_string[2:]
                _option_strings.append(option_string)

     ...

    def __call__(self, parser, namespace, values, option_string=None):
        if option_string in self.option_strings:
            setattr(namespace, self.dest, not option_string.startswith('--no-'))

So the action __init__ defines the two flags, and the __call__ checks for the no part.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement