Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Argparse optional boolean [duplicate]

I am trying to get the following behaviour:

  • python test.py ⟹ store foo=False
  • python test.py --foo ⟹ store foo=True
  • python test.py --foo bool ⟹ store foo=bool

It works when I use

parser.add_argument('--foo', nargs='?', default=False, const=True) 

However, it breaks if I add type=bool, trying to enforce casting to boolean. In this case

python test.py --foo False 

Actually ends up storing foo=True. What's going on??

like image 684
Hyperplane Avatar asked Sep 19 '18 09:09

Hyperplane


People also ask

How do you add an optional argument in Argparse?

Optional Arguments To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

How do you pass a boolean value in Argparse Python?

The boolean value is always assigned, so that it can be used in logical statements without checking beforehand: import argparse parser = argparse. ArgumentParser(description="Parse bool") parser. add_argument("--do-something", default=False, action="store_true", help="Flag to do something") args = parser.

How do I make Argparse argument optional in Python?

Python argparse optional argument The example adds one argument having two options: a short -o and a long --ouput . These are optional arguments. The module is imported. An argument is added with add_argument .

How do I pass a boolean to a Python script?

You can either use the action with store_true | store_false , or you can use an int and let implicit casting check a boolean value. Using the action , you wouldn't pass a --foo=true and --foo=false argument, you would simply include it if it was to be set to true.


1 Answers

Are you sure you need that pattern? --foo and --foo <value>, together, for a boolean switch, is not a common pattern to use.

As for your issue, remember that the command line value is a string and, type=bool means that you want bool(entered-string-value) to be applied. For --foo False that means bool("False"), producing True; all non-empty strings are true! See Why is argparse not parsing my boolean flag correctly? as well.

Instead of supporting --foo / --foo <string value>, I would strongly recommend you use --foo to mean True, drop the argument value, and instead add a --no-foo option to explicitly set False:

parser.add_argument('--foo', default=False, action='store_true') parser.add_argument('--no-foo', dest='foo', action='store_false') 

The dest='foo' addition on the --no-foo switch ensures that the False value it stores (via store_false) ends up on the same args.foo attribute.

As of Python 3.9, you can also use the argparse.BooleanOptionalAction action class:

parser.add_argument("--foo", action=argparse.BooleanOptionalAction) 

and it'll have the same effect, handling --foo and --no-foo to set and clear the flag.

You'd only need a --foo / --no-foo combination if you have some other configuration mechanism that would set foo to True and you needed to override this again with a command-line switch. --no-<option> is a widely adopted standard to invert a boolean command-line switch.

If you don't have a specific need for a --no-foo inverted switch (since just omitting --foo would already mean 'false'), then just stick with the action='store_true' option. This keeps your command line simple and clear!

However, if your use case or other constraints specifically require that your command line must have some king of --foo (true|false|0|1) support, then add your own converter:

def str_to_bool(value):     if isinstance(value, bool):         return value     if value.lower() in {'false', 'f', '0', 'no', 'n'}:         return False     elif value.lower() in {'true', 't', '1', 'yes', 'y'}:         return True     raise ValueError(f'{value} is not a valid boolean value')  parser.add_argument('--foo', type=str_to_bool, nargs='?', const=True, default=False) 
  • the const value is used for nargs='?' arguments where the argument value is omitted. Here that sets foo=True when --foo is used.
  • default=False is used when the switch is not used at all.
  • type=str_to_bool is used to handle the --foo <value> case.

Demo:

$ cat so52403065.py from argparse import ArgumentParser  parser = ArgumentParser()  def str_to_bool(value):     if value.lower() in {'false', 'f', '0', 'no', 'n'}:         return False     elif value.lower() in {'true', 't', '1', 'yes', 'y'}:         return True     raise ValueError(f'{value} is not a valid boolean value')  parser.add_argument('--foo', type=str_to_bool, nargs='?', const=True, default=False)  print(parser.parse_args()) $ python so52403065.py Namespace(foo=False) $ python so52403065.py --foo Namespace(foo=True) $ python so52403065.py --foo True Namespace(foo=True) $ python so52403065.py --foo no Namespace(foo=False) $ python so52403065.py --foo arrbuggrhellno usage: so52403065.py [-h] [--foo [FOO]] so52403065.py: error: argument --foo: invalid str_to_bool value: 'arrbuggrhellno' 
like image 180
Martijn Pieters Avatar answered Sep 28 '22 11:09

Martijn Pieters