In Python argparse, is it possible to have paired --no-something/--something arguments?
Well, none of the answers so far are quite satisfactory for a variety of reasons. So here is my own answer:
class ActionNoYes(argparse.Action):
def __init__(self, opt_name, dest, default=True, required=False, help=None):
super(ActionNoYes, self).__init__(['--' + opt_name, '--no-' + opt_name], dest, nargs=0, const=None, default=default, required=required, help=help)
def __call__(self, parser, namespace, values, option_string=None):
if option_string.starts_with('--no-'):
setattr(namespace, self.dest, False)
else:
setattr(namespace, self.dest, True)
And an example of use:
>>> p = argparse.ArgumentParser()
>>> p._add_action(ActionNoYes('foo', 'foo', help="Do (or do not) foo. (default do)"))
ActionNoYes(option_strings=['--foo', '--no-foo'], dest='foo', nargs=0, const=None, default=True, type=None, choices=None, help='Do (or do not) foo. (default do)', metavar=None)
>>> p.parse_args(['--no-foo', '--foo', '--no-foo'])
Namespace(foo=False)
>>> p.print_help()
usage: -c [-h] [--foo]
optional arguments:
-h, --help show this help message and exit
--foo, --no-foo Do (or do not) foo. (default do)
Unfortunately, the _add_action
member function isn't documented, so this isn't 'official' in terms of being supported by the API. Also, Action
is mainly a holder class. It has very little behavior on its own. It would be nice if it were possible to use it to customize the help message a bit more. For example saying --[no-]foo
at the beginning. But that part is auto-generated by stuff outside the Action
class.
I modified the solution of @Omnifarious to make it more like the standard actions:
import argparse
class ActionNoYes(argparse.Action):
def __init__(self, option_strings, dest, default=None, required=False, help=None):
if default is None:
raise ValueError('You must provide a default with Yes/No action')
if len(option_strings)!=1:
raise ValueError('Only single argument is allowed with YesNo action')
opt = option_strings[0]
if not opt.startswith('--'):
raise ValueError('Yes/No arguments must be prefixed with --')
opt = opt[2:]
opts = ['--' + opt, '--no-' + opt]
super(ActionNoYes, self).__init__(opts, dest, nargs=0, const=None,
default=default, required=required, help=help)
def __call__(self, parser, namespace, values, option_strings=None):
if option_strings.startswith('--no-'):
setattr(namespace, self.dest, False)
else:
setattr(namespace, self.dest, True)
You can add the Yes/No argument as you would add any standard option. You just need to pass ActionNoYes
class in the action
argument:
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action=ActionNoYes, default=False)
Now when you call it:
>> args = parser.parse_args(['--foo'])
Namespace(foo=True)
>> args = parser.parse_args(['--no-foo'])
Namespace(foo=False)
>> args = parser.parse_args([])
Namespace(foo=False)
Does the add_mutually_exclusive_group()
of argparse
help?
parser = argparse.ArgumentParser()
exclusive_grp = parser.add_mutually_exclusive_group()
exclusive_grp.add_argument('--foo', action='store_true', help='do foo')
exclusive_grp.add_argument('--no-foo', action='store_true', help='do not do foo')
args = parser.parse_args()
print 'Starting program', 'with' if args.foo else 'without', 'foo'
print 'Starting program', 'with' if args.no_foo else 'without', 'no_foo'
Here's how it looks when run:
./so.py --help
usage: so.py [-h] [--foo | --no-foo]
optional arguments:
-h, --help show this help message and exit
--foo do foo
--no-foo do not do foo
./so.py
Starting program without foo
Starting program without no_foo
./so.py --no-foo --foo
usage: so.py [-h] [--foo | --no-foo]
so.py: error: argument --foo: not allowed with argument --no-foo
This is different from the following in the mutually exclusive group allows neither option in your program (and I'm assuming that you want options because of the --
syntax). This implies one or the other:
parser.add_argument('--foo=', choices=('y', 'n'), default='y',
help="Do foo? (default y)")
If these are required (non-optional), maybe using add_subparsers()
is what you're looking for.
Update 1
Logically different, but maybe cleaner:
...
exclusive_grp.add_argument('--foo', action='store_true', dest='foo', help='do foo')
exclusive_grp.add_argument('--no-foo', action='store_false', dest='foo', help='do not do foo')
args = parser.parse_args()
print 'Starting program', 'with' if args.foo else 'without', 'foo'
And running it:
./so.py --foo
Starting program with foo
./so.py --no-foo
Starting program without foo
./so.py
Starting program without foo