6

I'm using argparse to manage command line options, and I want manage two options, --check and --nocheck. I'm actually doing something like this in mybadscript.py:

[...]
args = parser.parse_args()

if args.check:
    check = True

if args.nocheck:
    check = False
[...]

The problem is that if launch the script this way:

python mybadscript.py --nocheck --check

check will be set to False. This is incorrect, since the last option is --check.

How can I correctly manage them?

PS: I know you can easily avoid it using only one option, but I would know how I can manage option precedence with argparse, since you can encounter it in more complicated scenarios.

PPS: the suggested answer does incidentally answer my question, but the related question is not the same one.

Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
  • 1
    Possible dupe? http://stackoverflow.com/q/15008758/748858 – mgilson Sep 17 '15 at 16:35
  • The basic point is that your user controls the precedence - the last option string has final say on the value. If you don't like that, you have to use specialized `action` classes and `dest` values to detect and use earlier strings. – hpaulj Sep 17 '15 at 23:37

2 Answers2

7

See the argparse documentation. Here's an example of what you might want. I've included several options you may not need—I thought that was better than leaving them off and you needing them.

>>> parser = argparse.ArgumentParser()
>>> group = parser.add_mutually_exclusive_group(required=True)
>>> group.add_argument('--check', action='store_true', dest="check")
>>> group.add_argument('--nocheck', action='store_false', dest="check")
>>> parser.parse_args(["--check"])
Namespace(check=True)
>>> parser.parse_args(["--nocheck"])
Namespace(check=False)

You may not want the mutually exclusive part—in that case delete the second line and replace group with parser. If you do so you may wish to add this line:

>>> parser.set_defaults(check=False)
robert
  • 33,242
  • 8
  • 53
  • 74
  • 1
    The mutually exclusive group sidesteps the actual question, since now it's not possible to use both, making the question of precedence moot. Otherwise, good answer. I'd use `set_defaults` to set the value of `check` in case *neither* option is used. – chepner Sep 17 '15 at 16:38
  • @chepner I added a reference to `set_defaults`, but in my second line I say `required=True`, sidestepping the issue of the user not supplying an preference. – robert Sep 17 '15 at 16:42
  • Thank you, I missed `dest`. Anyway this does not fullfill completely my curiosity, but the actual question is answered. I'll open another question about option position. – Marco Sulla Sep 17 '15 at 20:26
  • @MarcoSulla I believe my explanation after the code covers the rest of your question—if you don't use the `add_mutually_exclusive_group` part you'll simply get the value corresponding to the most recent option. – robert Sep 17 '15 at 22:31
  • Yes, indeed I have a new one :) ----> http://stackoverflow.com/questions/33264649/cli-option-that-is-an-aggregation-of-two-other-options-in-python – Marco Sulla Oct 21 '15 at 16:32
1

You have two arguments that independently set two different Namespace attributes, args.check and args.nocheck. With --nocheck --check input line, both are set.

In:

if args.check:
    check = True
if args.nocheck:
    check = False

you test nocheck last, so that one overrides check.

argparse tries to handle optionals (flagged arguments) in an order independent manner.

If they write to the same 'dest', it's the last one that you will end up seeing. One way to see what was entered, and in what order, is to use an 'append' action:

parser=argparse.ArgumentParser()
parser.add_argument('--check', dest='checked', action='append_const', const=True)
 parser.add_argument('--nocheck', dest='checked', action='append_const', const=False)

In [14]: parser.parse_args(''.split()) # no arg
Out[14]: Namespace(checked=None)

I could have made the default []

In [15]: parser.parse_args('--check'.split())
Out[15]: Namespace(checked=[True])

In [16]: parser.parse_args('--nocheck'.split())
Out[16]: Namespace(checked=[False])

In [17]: parser.parse_args('--nocheck --check --nocheck'.split())
Out[17]: Namespace(checked=[False, True, False])

The paired 'store_true' and 'store_false' in the other answer is simpler.

Better yet, choose one as the default choice, and just provide an option for the other. As long as the default is clear, there really isn't a need for both arguments.

hpaulj
  • 221,503
  • 14
  • 230
  • 353