Argparse optional boolean [duplicate]
I am trying to get the following behaviour:
-
python test.py
⟹ storefoo=False
-
python test.py --foo
⟹ storefoo=True
-
python test.py --foo bool
⟹ storefoo=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??
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 fornargs='?'
arguments where the argument value is omitted. Here that setsfoo=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'
You should use the action='store_true'
parameter instead for Boolean arguments:
parser.add_argument('--foo', action='store_true')
So that the absence of the --foo
option:
python test.py
would result in a False
value for the foo
argument, and the presence of the --foo
option:
python test.py --foo
would result in a True
value for the foo
argument.