Parsing Boolean Values with Argparse

Parsing boolean values with argparse

This is actually outdated. For Python 3.7+, Argparse now supports boolean args (search BooleanOptionalAction).

The implementation looks like this:

import argparse

ap = argparse.ArgumentParser()

# List of args
ap.add_argument('--foo', default=True, type=bool, help='Some helpful text that is not bar. Default = True')

# Importable object
args = ap.parse_args()

One other thing to mention: this will block all entries other than True and False for the argument via argparse.ArgumentTypeError. You can create a custom error class for this if you want to try to change this for any reason.

Cannot parse boolean values with argparse

The way 'store_true' works is that if you give --download as an argument, then the value is true; and if you omit it, it is false.

The reason type=bool doesn't work as you want is that any nonempty string passed to the bool function will result in True. (You could, if you wanted, write a function that returns True if the string is "True" and False if it is "False", and use that for type, but that is not the typical use-case.)

Argparse not parsing boolean arguments?

You are misunderstanding how the argparse understands the boolean arguments.

Basically you should use action='store_true' or action='store_false' instead of the default value, with the understanding that not specifying the argument will give you the opposite of the action, e.g.

parser.add_argument('-x', type=bool, action='store_true')

will cause:

python3 command -x

to have x set to True and

python3 command

to have x set to False.

While action=store_false will do the opposite.


Setting bool as type does not behave as you expect and this is a known issue.

The reason for the current behavior is that type is expected to be a callable which is used as argument = type(argument). bool('False') evaluates to True, so you need to set a different type for the behavior you expect to happen.

Argparse optional boolean

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'

Why is argparse not parsing my boolean flag correctly?

You are trying to turn the string "False" into a boolean:

>>> bool("False")
True

That won't work because the string "False" is a non-empty value. All non-empty strings have a True boolean value.

Use a store_false action instead:

parser.add_argument('--disable-feature', dest='feature', 
action='store_false')

Now when you use that switch, False is stored, otherwise the default is True (set by action='store_false').

R: Parsing boolean command line argument in using argparse

I don't know R, but the description of this package says it's a wrapper for the Python argparse.

I would recommend changing these:

parser$add_argument("is_local", nargs='?', type="logical", 
help="whether to use local or server path", default=FALSE)
parser$add_argument("alert", nargs='?', type="double",
help="alert threshold", default=0.99)

to

parser$add_argument("--local", action='store_true'), 
help="whether to use local or server path")
parser$add_argument("--alert", type="double",
help="alert threshold", default=0.99)

which would be called with

Rscript my_func.R --local --alert 0.99

store_true is illustrated on the basic docs page, https://github.com/trevorld/r-argparse

If I read the R correctly, your is_local should be giving you a warning

"You almost certainly want to use action='store_true' or action='store_false' instead"

A store_true argument sets the attribute to TRUE if present, and the default FALSE if absent. It should be an optional (--) and not set the nargs.

(It is possible to have an argument that takes strings 'true' and 'false' (or any other pair in your native language) and converts them to logical values, but requires more coding.)

I made --alert a flagged argument as well, without the nargs. Its value will be the default if absent, and the convert the string to a double if provided. It could be a '?' positional, but while learning I think it's best to stick with optionals unless you want the argument to be required.

The R-argparse docs aren't very complete. You may need to refer to the Python docs, and experiment to get the translation right.

https://docs.python.org/3/library/argparse.html

Why in argparse, a 'True' is always 'True'?

You are not passing in the False object. You are passing in the 'False' string, and that's a string of non-zero length.

Only a string of length 0 tests as false:

>>> bool('')
False
>>> bool('Any other string is True')
True
>>> bool('False') # this includes the string 'False'
True

Use a store_true or store_false action instead. For default=True, use store_false:

parser.add_argument('--bool', default=True, action='store_false', help='Bool type')

Now omitting the switch sets args.bool to True, using --bool (with no further argument) sets args.bool to False:

python test.py
True

python test.py --bool
False

If you must parse a string with True or False in it, you'll have to do so explicitly:

def boolean_string(s):
if s not in {'False', 'True'}:
raise ValueError('Not a valid boolean string')
return s == 'True'

and use that as the conversion argument:

parser.add_argument('--bool', default=True, type=boolean_string, help='Bool type')

at which point --bool False will work as you expect it to.



Related Topics



Leave a reply



Submit