Parsing boolean values with argparse
I would like to use argparse to parse boolean command-line arguments written as "--foo True" or "--foo False". For example:
my_program --my_boolean_flag False
However, the following test code does not do what I would like:
import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
parsed_args = parser.parse(cmd_line)
Sadly, parsed_args.my_bool
evaluates to True
. This is the case even when I change cmd_line
to be ["--my_bool", ""]
, which is surprising, since bool("")
evalutates to False
.
How can I get argparse to parse "False"
, "F"
, and their lower-case variants to be False
?
Solution 1:
I think a more canonical way to do this is via:
command --feature
and
command --no-feature
argparse
supports this version nicely:
parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
Of course, if you really want the --arg <True|False>
version, you could pass ast.literal_eval
as the "type", or a user defined function ...
def t_or_f(arg):
ua = str(arg).upper()
if 'TRUE'.startswith(ua):
return True
elif 'FALSE'.startswith(ua):
return False
else:
pass #error condition maybe?
Solution 2:
Yet another solution using the previous suggestions, but with the "correct" parse error from argparse
:
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
This is very useful to make switches with default values; for instance
parser.add_argument("--nice", type=str2bool, nargs='?',
const=True, default=False,
help="Activate nice mode.")
allows me to use:
script --nice
script --nice <bool>
and still use a default value (specific to the user settings). One (indirectly related) downside with that approach is that the 'nargs' might catch a positional argument -- see this related question and this argparse bug report.
Solution 3:
If you want to allow --feature
and --no-feature
at the same time (last one wins)
This allows users to make a shell alias with --feature
, and overriding it with --no-feature
.
Python 3.9 and above
parser.add_argument('--feature', default=True, action=argparse.BooleanOptionalAction)
Python 3.8 and below
I recommend mgilson's answer:
parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
If you DON'T want to allow --feature
and --no-feature
at the same time
You can use a mutually exclusive group:
feature_parser = parser.add_mutually_exclusive_group(required=False)
feature_parser.add_argument('--feature', dest='feature', action='store_true')
feature_parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
You can use this helper if you are going to set many of them:
def add_bool_arg(parser, name, default=False):
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--' + name, dest=name, action='store_true')
group.add_argument('--no-' + name, dest=name, action='store_false')
parser.set_defaults(**{name:default})
add_bool_arg(parser, 'useful-feature')
add_bool_arg(parser, 'even-more-useful-feature')
Solution 4:
Here is another variation without extra row/s to set default values. 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.parse_args()
if args.do_something:
print("Do something")
else:
print("Don't do something")
print(f"Check that args.do_something={args.do_something} is always a bool.")