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 argparseclass 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 --helpusage: 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.pyStarting program without fooStarting program without no_foo./so.py --no-foo --foousage: 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 --fooStarting program with foo./so.py --no-fooStarting program without foo./so.pyStarting program without foo