9

Is there a straightforward way to use --toggle and --no-toggle flags with Python's argparse?

Right now I'm using something similar to the following:

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('--toggle',
                    action='store_true',
                    dest='toggle')
parser.add_argument('--no-toggle',
                    action='store_true',
                    default=True,
                    dest='notoggle')

options = parser.parse_args([])

I'm just manually parsing out the possibilities in a long if chain, but it would be nice if there was a way to tidy this up and have the state immediately stored in one destination by the parser, e.g. options.toggle. Is this feasible and if so, how?

A somewhat related answer is Python argparse toggle flags however I'm interested in using --no- as the longopts store_false toggle prefix (similar to the - shortopts toggle prefix outlined in the aforementioned link).

Six
  • 5,122
  • 3
  • 29
  • 38
  • After parsing what do you do with `options.toggle` and `options.notoggle`? Is there some logical relation - e.g. one negates the other; over rides it? Can the user give both arguments, in either order. So far the only connection is in name similarity. There isn't any feature in `argparse` like this, in part because there aren't any common usage patterns. – hpaulj Jan 12 '16 at 06:40
  • 1
    Does this answer your question? [In Python argparse, is it possible to have paired --no-something/--something arguments?](https://stackoverflow.com/questions/9234258/in-python-argparse-is-it-possible-to-have-paired-no-something-something-arg) – Calimo Jan 24 '22 at 07:41

2 Answers2

12

Why not do the same as the post you linked to??

import argparse

class NegateAction(argparse.Action):
    def __call__(self, parser, ns, values, option):
        setattr(ns, self.dest, option[2:4] != 'no')

ap = argparse.ArgumentParser()
ap.add_argument('--toggle', '--no-toggle', dest='toggle', action=NegateAction, nargs=0)

Then if the flag has no at the beginning it is set to False:

>>> ap.parse_args(['--toggle'])
Namespace(toggle=True)
>>> options = ap.parse_args(['--no-toggle'])
>>> options.toggle
False
AChampion
  • 29,683
  • 4
  • 59
  • 75
9

Arguments can use the same dest:

import argparse
parser = argparse.ArgumentParser()

parser.add_argument('--toggle',
                    action='store_const',
                    default = 'unknown',
                    const = 'yes',  # const=True also works
                    dest='toggle')
parser.add_argument('--no-toggle',
                    action='store_const',
                    const = 'no',   # or False
                    dest='toggle')

This produces:

In [60]: parser.parse_args([])    # none
Out[60]: Namespace(toggle='unknown')
In [61]: parser.parse_args(['--tog'])
Out[61]: Namespace(toggle='yes')
In [62]: parser.parse_args(['--no-tog'])
Out[62]: Namespace(toggle='no')
In [63]: parser.parse_args(['--no-tog','--tog'])  # more than one
Out[63]: Namespace(toggle='yes')

I used store_const rather than store_true and store_false to make the 3 alternatives clear. store_true is just store_const with const=True and default=False.

If you define a store_true and store_false pair, it is hard to distinguish between the no argument case and --no-toggle case. --no-toggle doesn't do anything significant, unless it follows a --toggle argument.

If you need to define a large set of these pairs, you can easily define a helper function, which takes a couple of the parameters, and creates the pair of arguments.

hpaulj
  • 221,503
  • 14
  • 230
  • 353