Parsing boolean values with argparse
Asked Answered
B

28

1103

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?

Bike answered 21/2, 2013 at 17:37 Comment(6)
Here is a one-liner interpretation of @mgilson's answer parser.add_argument('--feature', dest='feature', default=False, action='store_true'). This solution will gurantee you always get a bool type with value True or False. (This solution has a constraint: your option must have a default value.)Chinkiang
Here is a one-liner interpretation of @Maxim's answer parser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))). When the option is used, this solution will ensure a bool type with value of True or False. When the option is not used you will get None. (distutils.util.strtobool(x) is from another stackoverflow question)Chinkiang
how about something like parser.add_argument('--my_bool', action='store_true', default=False)Ashkhabad
For answer by @TrevorBoydSmith , try import with import distutils.util instead of import disutils. See this answerJuniejunieta
Just ran into the same issue. It's astounding how unnecessarily big and overgrown the argparse module is, and still, it does not do simple things it's supposed to do out of the box. Even worse, it's doing them wrongly.Chapel
@AnatolyAlekseev, argparse developers are fully aware that some users try to handle strings like "True" or "False" as booleans, but have rejected proposals to redefine the basic Python bool function. Parsing words that can represent True/False is too language specific, and is best left to the programmer (and not hard). Simply put the type parameter is a function.Gazebo
F
41

Simplest & most correct way is:

from distutils.util import strtobool

parser.add_argument('--feature', dest='feature', 
                    type=lambda x: bool(strtobool(x)))

Do note that True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. Raises ValueError if val is anything else.

Francisco answered 3/1, 2020 at 13:49 Comment(1)
This should be so much higher up! :-)Beneficent
T
1491

I think a more canonical way to do this is via:

command --feature

and

command --no-feature

argparse supports this version nicely:

Python 3.9+:

parser.add_argument('--feature', action=argparse.BooleanOptionalAction)

Python < 3.9:

parser.add_argument('--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?
Toole answered 21/2, 2013 at 17:40 Comment(41)
I still think type=bool should work out of the box (consider positional arguments!). Even when you additionally specify choices=[False,True], you end up with both "False" and "True" considered True (due to a cast from string to bool?). Maybe related issueCelio
@Celio -- You could use type = {'True':True,'False':False}.get but remember, bool('False') is True, so passing bool as the type constructor function doesn't work here.Toole
Right, I just think there is no justification for this not working as expected. And this is extremely misleading, as there are no safety checks nor error messages.Celio
@Celio -- What do you mean "for this not working as expected"? As I see it, this works exactly as expected. The type argument operates on the string input from the commandline to construct the output. And what are you referring to as misleading? Is there something in my answer that could be improved? If so, please let me know specifically and I'll try to clarify. Ultimately though, argparse is structured because most programs don't have flags like --arg True|False. What OP is looking for here is non-standard which is why it's not provided by argparse.Toole
Read about it in the docs: docs.python.org/2.7/library/argparse.html#actionRancell
@Toole -- What I find misleading is that you can set type=bool, you get no error message, and yet, for both "False" and "True" string arguments, you get True in your supposedly boolean variable (due to how type casting works in python). So either type=bool should be clearly unsupported (emit some warning, error, etc.), or it should work in a way that is useful and intuitively expected.Celio
@Celio -- respectively, I disagree. I think that the behavior is exactly the way it should be and is consistent with the zen of python "Special cases aren't special enough to break the rules". However, if you feel this strongly about it, why not bring it up on one of the various python mailing lists? There, you might have a chance at convincing someone who has the power to do something about this issue. Even if you were able to convince me, you will have only succeeded in convincing me and the behavior still won't change since I'm not a dev:)Toole
@Toole Well, there are various python rules, and whichever you apply, the current solution for type=bool arguments is useless and misleading (or am I missing any use of the current behavior of type=bool?) Isn't it also inconsistent with the behavior of, for example, int args? (you get 0 for "0" and 5 for "5" I guess). I don't find it the highest priority issue, just feel the current solution for bool is not as useful as it could be, and may - for some programmers - lead to unexpected results in their apps.Celio
Are we arguing about what the Python bool() function should do, or what argparse should accept in type=fn? All argparse checks is that fn is callable. It expects fn to take one string argument, and return a value. The behavior of fn is the programer's responsibility, not argparse's.Gazebo
This behavior is definitely misleading: it works for str, works for int, but fails for bool ?!Vu
@ChristopheRoussy -- I still think this is the best behavior... You're looking for python to do magic. (based on the popularity of this answer, a lot of people probably are). However, the problem with magic is that everyone expects it to behave differently. What do you accept? True, False? What about true? What about yes/no? How about Si for the Spanish speakers? Figuring out where to draw the line there gets difficult. I argue that in this case, it's best to do the minimum and force the user to be explicit.Toole
@Toole I would expect it to show allowed values in the help 'true or false (any case)' OR it should fail and refuse bool. I am quite new to python, so this is what I would expect from a tool which is supposed to handle standard cases like str, int, bool, ... I understand it is somewhat magical due to the case handling...Vu
While the --no-feature version is nice, the support for --feature=False to work as no argument specified has very valid use case: when the boolean value of the flag is prepared by a client and say the program is called with --feature=$VALUE.Resource
Instead of ua == 'TRUE'[:len(ua)], you should use 'TRUE'.startswith(ua). This is not only clearer, and is also more efficient since you don't create a temporary string for the comparison.Depressive
@TomKarzes -- Yeah, I'm not sure why 3 year younger me didn't think of that :-P. It also makes me wonder how many of the 64.5k users who have seen this answer have just blindly copy/pasted that into their code ...Toole
Hi, all, sorry, after check all the discussion, then I did not understand what is the best solution for this, I tried to replace type = bool to type = {'True':True,'False':False}.get does not work. Must we use "-- feature" or define a function like "def t_or_f(arg)" in the reply to do the trick?Oread
@Oread -- As far as I'm aware, those are the best ways to do it. Is there any reason that you don't want to do it that way?Toole
The comments on this answer are ridiculous. Why would anyone expect bool('my non-empty, and truthy string') to return False? If you wouldn't, why would expect bool('False') to return True? Do you not understand the argument passed to type= is just a function (or any callable, really)? That's why int works - because int('5') returns an integer. str('foo') works because str returns a string. What is so controversial about this?? Perhaps type is a bad name for the keyword argument - maybe parse_func or something would make it more obvious its not magic??Daguerre
@JoshBurkart -- Maybe it's unintuitive, though calling it idiotic is perhaps a bit insulting to people who have different opinions than you (people who have donated LOTS of time to making a pretty nice commandline parsing library). In any case, if you and the bunch of other commenters/upvoters feel really passionate about this, why not propose it to the library maintainers (rather than posting here which is unlikely to ever reach the eyes/ears of people who actually have the power to change something?)Toole
@Toole My comment was indeed insulting and deeply inappropriate. And not at all constructive, as you point out. I totally apologize.Picrate
Boolean flags are an extremely common use case, so a one-line solution seems very desirable to me. There is also no reason IMO for a user to expect that setting type=bool would cause the parser to be the bool function. (Indeed, it seems unlikely that a use case exists for making a boolean flag that interprets "blah random string" as True...?) I'll consider trying to contribute to improving this in the future! :)Picrate
I don't understand why you have to have an argument for both sides of a boolean. Add an argument for the non-default case. Default to the default case. Done. parser.add_argument('-n', '--no-feature', dest='nofeature', default=False, action='store_true'); ...; feature_bool = not parser.parse_args().nofeature Why make the user type in the default case?Chesterchesterfield
@Chesterchesterfield -- I'll tell you the same thing that I've told everyone else on this post. If you don't like the API and have a suggestion for a clean way to improve it, why not post an idea to python-ideas? There you'll have an audience that is empowered to consider you idea and either implement it or give you a reason for why they won't. It'll be much more productive than complaining to me (some random guy on the internet who happens to know a thing or two about Python).Toole
@Celio I think that depends on which language the user interface is in, right? For example, if you release a French version of your script, maybe (or should I say probably) you don't want "True" and "False" to map to the boolean values True and False, therefore it cannot be something that is built in to argparse.Giddings
@Giddings We are talking about the Boolean type here, with False and True being the only allowed values, even in French. Sure one can use str or implement a custom mapping from str to bool if one wants (type=bool_from_french?). By the way, even in English you can think about Off/On or No/Yes. You say "it cannot be something that is built in to argparse", yet what we have now is already built in and it works, but in a misleading way.Celio
@Celio I'm not sure I follow you. When I say French version of the script, I'm talking about the language of the user interface (not of the language the code is written in), and then you would use other words than "True" and "False," because they are English words and would obviously not be used in a French user interface. If someone wrote "True" when using a French user interface, it should probably be considered an invalid input.Giddings
@Celio What I'm saying is, I just don't see how argparse could possibly know what words you consider should be mapped to True and which words should be mapped to False, since this is language dependent, and possibly dependent on other things as well (like industry, perhaps). argparse is in first hand an argument parser, which is quite a straightforward task, and if you want something that understands written language, which can often be a much more complex and less straightforward task, I imagine it would go into some other package that could then be combined with argparse.Giddings
@Giddings If you use type=float and provide "3.14" (string), it gets converted to 3.14. If you use type=bool and provide "False" (string), it gets converted to True. A lot of people would call this an inconsistency and unexpected, misleading behavior, because float and bool are data types in Python.Celio
@Celio You've got a point. On the other hand, have you asked yourself why the behavior when converting a string to a boolean is like it currently is, i.e. resulting in True if and only if the string is non-empty (we are also talking about the behavior of Python itself now, and not the argparse package)? This behavior is obviously a conscious decision by the language creator(s), and is probably much more useful in practice than if it would return True if and only if the string is a word or a phrase that means true, as this is not an especially common use case in everyday programming.Giddings
@Giddings I have 30 years of experience in various programming languages, from machine code and assembly to Python, so I understand the reasons perfectly well. Just pointing out the inconsistency and the potential for misleading behavior and hidden/silent bugs. Also pointing out that if it worked "as expected", it would be useful for so many people.Celio
@Celio I'm not sure I'm getting your point. Are you arguing for the alternative behavior? So how would it work in practice? What strings do you think should be converted to True and what strings should be converted to False?Giddings
@Giddings since you are indicating what type you want (bool), I believe it is obvious. When you state that a parameter type is int, do you ask "what strings should be converted to -10", "what strings should be converted to 0" and "what strings should be converted to 52"?Celio
@Celio I usually don't, but someone has to ask that question at some point. In the case of conversion to bool by interpreting the semantics, I don't see how it can be obvious. Since you think it's obvious, could you please tell how you think the conversion should work?Giddings
@Giddings The conversion should work just like with int or float... you convert "0" to 0 and "5.23" to 5.23. Analogously, "True" to True and "False" to False. "x" causes an exception because one indicated that they expected a bool, just like "x" causes an exception when one indicated that they expected an int or a float.Celio
@Celio I have to admit that's actually a lot simpler than what I had imagined would be required, and because of that it could work. In imagined that things like f and t would have to work as well (which would be in line with how many yes/no questions in command line interfaces work, although exceptions exist), and perhaps other keywords as well, to make it more robust to usage variations. But then I also originally imagined that this was something that we wanted argparse to take care of, and not something that was supposed to be built into Python itself. Your point seems clearer now.Giddings
@Celio Also, when you think about it, it doesn't reaslly make that much sense that str(False) becomes 'False', but bool('False') becomes True. So yes, Python isn't super consistent in this regard.Giddings
for the --no-<flag> pattern argparse introduced the BooleanOptionalAction docs.python.org/3/library/argparse.html#actionMelly
In addition, to use variables in set_defaults() use parser.set_defaults(**{name: default}) where name and default are variables (not the actual names).Tucket
@Toole Just want to mention that type = {'True':True,'False':False}.get does not work for me. I'm instead just using @AruniRC's comment solution on the original question.Surveyor
If default value for feature is already False, why do we need '--no-feature' ?Selfpropulsion
It's very useful in scripting these CLIs to have the flexibility to declare exactly what behavior you want (then you aren't subject to the whims of anyone who decides to change the default).Toole
L
506

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.

Lamphere answered 11/4, 2017 at 23:45 Comment(14)
What is the need to specify nargs, the OP is asking for just one argument. Please mention if I am missing something. :)Dhammapada
nargs='?' means zero or one argument. docs.python.org/3/library/argparse.html#nargsLamphere
I love this, but my equivalent of default=NICE is giving me an error, so I must need to do something else.Rhody
here NICE is a variable holding the default value, i.e. True or False... Given this, I don't see what error you could be referring toLamphere
I'm using Python 3.6.1, str2bool is not a valid type; I need to use bool. Also if the default if False, then default= can be omitted.Pius
@MarcelloRomani str2bool is not a type in the Python sense, it is the function defined above, you need to include it somewhere.Lamphere
the code of str2bool(v) could be replaced with bool(distutils.util.strtobool(v)). Source: https://mcmap.net/q/45432/-converting-from-a-string-to-boolean-in-pythonCantoris
Maybe it is worth mentioning that with this way you cannot check if argument is set with if args.nice: beacuse if the argument is set to False, it will never pass the condition. If this is right then maybe it is better to return list from str2bool function and set list as const parameter, like this [True], [False]. Correct me if I am wrongEuphemism
What to do if we want to disallow no argument? I want the user to be able to say --nice 0 or --nice False or whatever, but they have to provide a value.Aubergine
This solution is very nice, but it will produce "None", if the argument is provided without a value. Should be False in that case.Alastair
@martin_wun did you try it? The default=False will produce False, not NoneLamphere
@Lamphere Yes, I did. I got "None" instead of "False". Default was set to "False" alright.Alastair
@martin_wun I'm surprised, as I tested it locally and got False, not None - here is some demo code ideone.com/yaPbWpLamphere
I've come here so many times that I had to give you an upvote, thank you.Voiceless
B
387

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')
Bullfinch answered 10/7, 2015 at 17:52 Comment(11)
how does the set_default know where to save the default?Lorineloriner
@CharlieParker add_argument is called with dest='feature'. set_defaults is called with feature=True. Understand?Bullfinch
This or mgilson's answer should have been the accepted answer - even though the OP wanted --flag False, part of SO answers should be about WHAT they're trying to solve, not just about HOW. There should be absolutely no reason to do --flag False or --other-flag True and then use some custom parser to convert the string to a boolean.. action='store_true' and action='store_false' are the best ways to use boolean flagsTurcotte
@kevlarr, While it's often very helpful to extrapolate about what the asker is trying to solve, SO is ultimately about answering the question as stated. (This has occasionally been a source of frustration for me on SO: not getting an answer to a simple question because all answerers were making assumptions about what I was trying to solve). That said, I do like this answer.Ferromagnesian
@Ferromagnesian Why is SO ultimately about answering "questions as stated"? According to its own guidelines, an anwer ... can be “don’t do that”, but it should also include “try this instead” which (at least to me) implies answers should go deeper when appropriate. There are definitely times when some of us posting questions can benefit from guidance on better/best practices, etc.. Answering "as stated" often doesn't do that. That being said, your frustration with answers often assuming too much (or incorrectly) is completely valid.Turcotte
If one wants to have a third value for when the user has not specified feature explicitly, he needs to replace the last line with the parser.set_defaults(feature=None)While
If we want to add a help= entry for this argument, where should it go? In the add_mutually_exclusive_group() call? In one or both of the add_argument() calls? Somewhere else?Phrygian
Nice thing about this option is you can set the default to None, and then have a tri-state bool, i.e. True,False of None!Sue
Mutually exclusive group is usually not a good idea in this case, because it prevents users from making a shell alias with --feature, and overriding it with --no-feature.Sita
for the --no-<flag> pattern argparse introduced the BooleanOptionalAction docs.python.org/3/library/argparse.html#actionMelly
This is neat -- too bad I didn't learn about this before you posted it so I could have gotten all these upvotes ;)Toole
D
130

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.")
Dionnadionne answered 24/5, 2016 at 11:16 Comment(7)
This answer is underrated, but wonderful in its simplicity. Don't try to set required=True or else you'll always get a True arg.Warder
Please NEVER use equality operator on things like bool or nonetype. You should use IS insteadIslas
This is a better answer than the accepted because it simply checks for the presence of the flag to set the boolean value, instead of requiring redundant boolean string. (Yo dawg, I heard you like booleans... so I gave you a boolean with your boolean to set your boolean!)Enlace
Hmm... the question, as stated, seems to want to use "True"/"False" on the command line itself; however with this example, python3 test.py --do-something False fails with error: unrecognized arguments: False, so it does not really answer the question.Kandykane
A trivial note: the default default of None will generally work fine here as well.Miscarriage
@Kandykane Just do below python3 test.py --do-something ---> this True python3 test.py ----> This is FalseGaloshes
Does some kind of conversion under the hood happen when using do-something (with dash) and using it in code args.do_something (underscore)?Mercurochrome
H
93

oneliner:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Hebraic answered 26/10, 2017 at 9:44 Comment(4)
good for oneliner fan, also it could be improved a bit: type=lambda x: (str(x).lower() in ['true','1', 'yes'])Weekley
Another option is to use the standard distutils.utils.strtobool, eg type=lambda x: bool(strtobool(str(x))) . True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0.Flunkey
Small correction @Jethro's comment: This should be distutils.util.strtobool (no s). Works great!Participation
strtobool is a bit annoying (especially in this context) in that it doesn't strip whitespace. So you need to do lambda x: strtobool(x.strip()). Otherwise you will crash on things like "false\r" if you have a new line in a bash script.Knucklehead
G
41

There seems to be some confusion as to what type=bool and type='bool' might mean. Should one (or both) mean 'run the function bool(), or 'return a boolean'? As it stands type='bool' means nothing. add_argument gives a 'bool' is not callable error, same as if you used type='foobar', or type='int'.

But argparse does have registry that lets you define keywords like this. It is mostly used for action, e.g. `action='store_true'. You can see the registered keywords with:

parser._registries

which displays a dictionary

{'action': {None: argparse._StoreAction,
  'append': argparse._AppendAction,
  'append_const': argparse._AppendConstAction,
...
 'type': {None: <function argparse.identity>}}

There are lots of actions defined, but only one type, the default one, argparse.identity.

This code defines a 'bool' keyword:

def str2bool(v):
  #susendberg's function
  return v.lower() in ("yes", "true", "t", "1")
p = argparse.ArgumentParser()
p.register('type','bool',str2bool) # add type keyword to registries
p.add_argument('-b',type='bool')  # do not use 'type=bool'
# p.add_argument('-b',type=str2bool) # works just as well
p.parse_args('-b false'.split())
Namespace(b=False)

parser.register() is not documented, but also not hidden. For the most part the programmer does not need to know about it because type and action take function and class values. There are lots of stackoverflow examples of defining custom values for both.


In case it isn't obvious from the previous discussion, bool() does not mean 'parse a string'. From the Python documentation:

bool(x): Convert a value to a Boolean, using the standard truth testing procedure.

Contrast this with

int(x): Convert a number or string x to an integer.

Gazebo answered 7/10, 2013 at 19:45 Comment(1)
Or use: parser.register('type', 'bool', (lambda x: x.lower() in ("yes", "true", "t", "1")))Resource
F
41

Simplest & most correct way is:

from distutils.util import strtobool

parser.add_argument('--feature', dest='feature', 
                    type=lambda x: bool(strtobool(x)))

Do note that True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. Raises ValueError if val is anything else.

Francisco answered 3/1, 2020 at 13:49 Comment(1)
This should be so much higher up! :-)Beneficent
B
31

A quite similar way is to use:

feature.add_argument('--feature',action='store_true')

and if you set the argument --feature in your command

 command --feature

the argument will be True, if you do not set type --feature the arguments default is always False!

Baez answered 16/3, 2016 at 9:35 Comment(2)
Is there some drawback to this method that the other answers overcome? This seems to be by far the easiest, most succinct solution that gets to what the OP (and in this case me) wanted. I love it.Epigrammatize
While simple, it does not answer the question. OP want an argument where you can specify --feature FalseTamper
U
21

I was looking for the same issue, and imho the pretty solution is :

def str2bool(v):
  return v.lower() in ("yes", "true", "t", "1")

and using that to parse the string to boolean as suggested above.

Urbanite answered 7/10, 2013 at 14:20 Comment(3)
If you're going to go this route, might I suggest distutils.util.strtobool(v).Annulet
The distutils.util.strtobool returns 1 or 0, not an actual boolean.Fulfillment
If you want a boolean from strtobool you could do lambda x: bool(strtobool(x.strip()). I would suggest that you at the very least extend this function so it only returns False when v.lower() in ("no", "false", "f", "0") and an error otherwise.Knucklehead
P
15

This works for everything I expect it to:

add_boolean_argument(parser, 'foo', default=True)
parser.parse_args([])                   # Whatever the default was
parser.parse_args(['--foo'])            # True
parser.parse_args(['--nofoo'])          # False
parser.parse_args(['--foo=true'])       # True
parser.parse_args(['--foo=false'])      # False
parser.parse_args(['--foo', '--nofoo']) # Error

The code:

def _str_to_bool(s):
    """Convert string to bool (in argparse context)."""
    if s.lower() not in ['true', 'false']:
        raise ValueError('Need bool; got %r' % s)
    return {'true': True, 'false': False}[s.lower()]

def add_boolean_argument(parser, name, default=False):                                                                                               
    """Add a boolean argument to an ArgumentParser instance."""
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        '--' + name, nargs='?', default=default, const=True, type=_str_to_bool)
    group.add_argument('--no' + name, dest=name, action='store_false')
Peacock answered 24/3, 2016 at 6:9 Comment(1)
Excellent! I'm going with this answer. I tweaked my _str_to_bool(s) to convert s = s.lower() once, then test if s not in {'true', 'false', '1', '0'}, and finally return s in {'true', '1'}.Zonnya
A
15

Simplest. It's not flexible, but I prefer simplicity.

  parser.add_argument('--boolean_flag',
                      help='This is a boolean flag.',
                      type=eval, 
                      choices=[True, False], 
                      default='True')

EDIT: If you don't trust the input, don't use eval.

Alleenallegation answered 3/4, 2020 at 16:51 Comment(7)
This does seem quite convenient. I noticed you have eval as the type. I had a question about this: how should eval be defined, or is there an import required in order to make use of it?Responsive
eval is a built-in function. docs.python.org/3/library/functions.html#eval This can be any unary function which other, more flexible approaches take advantage.Alleenallegation
Hey, that's great. Thanks!Responsive
that's cute, but quite risky to just put out into the wild where users who aren't aware of eval being evil will just copy-paste it into their scripts.Sadonia
@Arne, good point. Although, it appears that it would be pretty difficult for a well-intentioned user to accidentally do something pernicious.Alleenallegation
Do not use. Not only unsafe, the top answers are much more idiomatic. If you still want to go this route, a popular answer already mentioned: ast.literal_eval which is safer.Miscarriage
Not recommended. If users pass in some value like "no", "n", it will throw a NameError.Jovanjove
I
13

In addition to what @mgilson said, it should be noted that there's also a ArgumentParser.add_mutually_exclusive_group(required=False) method that would make it trivial to enforce that --flag and --no-flag aren't used at the same time.

Inaccessible answered 28/3, 2014 at 4:20 Comment(0)
A
11

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.

Anthropography answered 20/8, 2021 at 20:22 Comment(7)
You are mentioning this working in 3.7+ and 3.9+. Which one is it?Astera
3.7+. Clarified in an editAnthropography
The python documentation mentions New in version 3.9 and I cannot import BooleanOptionalAction from argparse in 3.7...Machiavellian
NO. That is not how action=argparse.BooleanOptionalAction works. Earlier under type, it warns against using the type=bool, "The bool() function is not recommended as a type converter. All it does is convert empty strings to False and non-empty strings to True. This is usually not what is desired.".Gazebo
Agreed, this answer should not be accepted: args = ap.parse_args(['--foo', 'False']) returns True (and in my opinion it shouldn'tPassim
Yeah I agree, I just got caught by this! I agree it should error instead of silently ignoring the False that follows --fooSue
"TypeError: _StoreTrueAction.__init__() got an unexpected keyword argument 'type'"Menken
H
10

A simpler way would be to use as below.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
Holeandcorner answered 27/12, 2016 at 5:44 Comment(0)
S
6

After previously following @akash-desarda 's excellence answer https://mcmap.net/q/53033/-parsing-boolean-values-with-argparse , to use strtobool via lambda, later, I decide to use strtobool directly instead.

import argparse
from distutils import util
parser.add_argument('--feature', type=util.strtobool)

Yes you're right, strtobool is returning an int, not a bool. But strtobool will not returning any other value except 0 and 1, and python will get them converted to a bool value seamlessy and consistently.

>>> 0 == False
True
>>> 0 == True
False
>>> 1 == False
False
>>> 1 == True
True

While on receiving a wrong input value like

python yours.py --feature wrong_value

An argparse.Action with strtobool compared to lambda will produce a slightly clearer/comprehensible error message:

yours.py: error: argument --feature: invalid strtobool value: 'wrong_value'

Compared to this code,

parser.add_argument('--feature', type=lambda x: bool(util.strtobool(x))

Which will produce a less clear error message:

yours.py: error: argument --feature: invalid <lambda> value: 'wrong_value'
Saving answered 19/5, 2021 at 10:48 Comment(0)
I
4

Simplest way would be to use choices:

parser = argparse.ArgumentParser()
parser.add_argument('--my-flag',choices=('True','False'))

args = parser.parse_args()
flag = args.my_flag == 'True'
print(flag)

Not passing --my-flag evaluates to False. The required=True option could be added if you always want the user to explicitly specify a choice.

Intracranial answered 12/6, 2019 at 13:26 Comment(0)
L
4

Expanding on gerardw's answer

The reason parser.add_argument("--my_bool", type=bool) doesn't work is that bool("mystring") is True for any non-empty string so bool("False") is actually True.

What you want is

my_program.py

import argparse

parser = argparse.ArgumentParser(description="My parser")

parser.add_argument(
        "--my_bool",
        choices=["False", "True"],
        )

parsed_args = parser.parse_args()

my_bool = parsed_args.my_bool == "True"

print(my_bool)
$ python my_program.py --my_bool False
False

$ python my_program.py --my_bool True
True

$ python my_program.py --my_bool true
usage: my_program.py [-h] [--my_bool {False,True}]
my_program.py: error: argument --my_bool: invalid choice: 'true' (choose from 'False', 'True')
Languid answered 14/3, 2022 at 15:24 Comment(0)
L
4

I found good way to store default value of parameter as False and when it is present in commandline argument then its value should be true.

cmd command when you want argument to be true: python main.py --csv

when you want your argument should be false: python main.py

import argparse
from ast import parse
import sys
parser = argparse.ArgumentParser(description='')
parser.add_argument('--csv', action='store_true', default = False 
,help='read from csv')
args = parser.parse_args()

if args.csv:
    print('reading from csv')
Logbook answered 23/6, 2022 at 13:11 Comment(1)
'store_true' already sets the default to False.Gazebo
H
4

Actually, very easy. No packages need to be imported.

Remember that type=func means returning the func(x), like type=bool -> bool(x), so:

parser.add_argument('--feature', default=False, type=lambda x: x == 'True')

Now that we have known the principle, you can also extend it to type=lambda x: x.lower() not in ['false', 'no', '0', 'none', ...].

Hooper answered 19/4, 2023 at 8:48 Comment(0)
P
3

As an improvement to @Akash Desarda 's answer, you could do

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument("--foo", 
    type=lambda x:bool(strtobool(x)),
    nargs='?', const=True, default=False)
args = parser.parse_args()
print(args.foo)

And it supports python test.py --foo

(base) [costa@costa-pc code]$ python test.py
False
(base) [costa@costa-pc code]$ python test.py --foo 
True
(base) [costa@costa-pc code]$ python test.py --foo True
True
(base) [costa@costa-pc code]$ python test.py --foo False
False
Prank answered 20/5, 2020 at 12:58 Comment(0)
C
2

I think the most canonical way will be:

parser.add_argument('--ensure', nargs='*', default=None)

ENSURE = config.ensure is None
Ceramal answered 24/2, 2017 at 13:41 Comment(0)
S
1

Quick and easy, but only for arguments 0 or 1:

parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)))
myargs=parser.parse_args()
print(myargs.mybool)

The output will be "False" after calling from terminal:

python myscript.py 0
Sphagnum answered 18/10, 2019 at 14:9 Comment(1)
This is the best method, 0 and 1 are easily interpretable as False and True. However, you should correct your first statement to say 0 will return false and any other value will return True. If you do want to restrict to 0,1 then add a choices as follows: parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)), choices=['0','1'])Larocca
U
0
class FlagAction(argparse.Action):
    # From http://bugs.python.org/issue8538

    def __init__(self, option_strings, dest, default=None,
                 required=False, help=None, metavar=None,
                 positive_prefixes=['--'], negative_prefixes=['--no-']):
        self.positive_strings = set()
        self.negative_strings = set()
        for string in option_strings:
            assert re.match(r'--[A-z]+', string)
            suffix = string[2:]
            for positive_prefix in positive_prefixes:
                self.positive_strings.add(positive_prefix + suffix)
            for negative_prefix in negative_prefixes:
                self.negative_strings.add(negative_prefix + suffix)
        strings = list(self.positive_strings | self.negative_strings)
        super(FlagAction, self).__init__(option_strings=strings, dest=dest,
                                         nargs=0, const=None, default=default, type=bool, choices=None,
                                         required=required, help=help, metavar=metavar)

    def __call__(self, parser, namespace, values, option_string=None):
        if option_string in self.positive_strings:
            setattr(namespace, self.dest, True)
        else:
            setattr(namespace, self.dest, False)
Ulrikaumeko answered 28/10, 2014 at 21:0 Comment(0)
A
0

Similar to @Akash but here is another approach that I've used. It uses str than lambda because python lambda always gives me an alien-feelings.

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument("--my_bool", type=str, default="False")
args = parser.parse_args()

if bool(strtobool(args.my_bool)) is True:
    print("OK")
Akee answered 27/4, 2020 at 6:20 Comment(0)
B
0

just do the following , you can make --test = True by using

python filename --test

parser.add_argument("--test" , default=False ,help="test ?", dest='test', action='store_true')
Bluing answered 17/9, 2020 at 10:23 Comment(1)
How is that different from this answer from 4 years ago?Calyces
N
0

Convert the value:

def __arg_to_bool__(arg):
    """__arg_to_bool__

        Convert string / int arg to bool
    :param arg: argument to be converted
    :type arg: str or int
    :return: converted arg
    :rtype: bool
    """
    str_true_values = (
        '1',
        'ENABLED',
        'ON',
        'TRUE',
        'YES',
    )
    str_false_values = (
        '0',
        'DISABLED',
        'OFF',
        'FALSE',
        'NO',
    )

    if isinstance(arg, str):
        arg = arg.upper()
        if arg in str_true_values:
            return True
        elif arg in str_false_values:
            return False

    if isinstance(arg, int):
        if arg == 1:
            return True
        elif arg == 0:
            return False

    if isinstance(arg, bool):
        return arg

    # if any other value not covered above, consider argument as False
    # or you could just raise and error
    return False

[...]


args = ap.parse_args()
my_arg = options.my_arg
my_arg = __arg_to_bool__(my_arg)

Note answered 8/1, 2021 at 8:19 Comment(0)
Y
0

You can create a BoolAction and then use it

class BoolAction(Action):
    def __init__(
            self,
            option_strings,
            dest,
            nargs=None,
            default: bool = False,
            **kwargs,
    ):
        if nargs is not None:
            raise ValueError('nargs not allowed')
        super().__init__(option_strings, dest, default=default, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        input_value = values.lower()
        b = input_value in ['true', 'yes', '1']
        if not b and input_value not in ['false', 'no', '0']:
            raise ValueError('Invalid boolean value "%s".)
        setattr(namespace, self.dest, b)

and then set action=BoolAction in parser.add_argument()

Yachting answered 1/11, 2022 at 5:58 Comment(1)
This is great! I would just note that you also need to not set type=bool when you call add_argument, or it will just pass True to the BoolActionBibliofilm
M
0
import argparse
if __name__=='__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--bool_flag', type=str)
    args = parser.parse_args()
    args.bool_flag = bool(eval(args.bool_flag))
    print(args.bool_flag, ',', type(args.bool_flag))

For the above python code saved as tmp.py:

python tmp.py --bool_flag '0' gives False , <class 'bool'>

python tmp.py --bool_flag 'False' gives False , <class 'bool'>

python tmp.py --bool_flag '1' gives True , <class 'bool'>

python tmp.py --bool_flag 'True' gives True , <class 'bool'>

Calling above code from a shell script would work too,

#!/bin/bash
flag=False
python tmp.py --bool_flag $flag

This will give the output: False , <class 'bool'>

Munch answered 2/3, 2024 at 9:17 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.