1046

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
SuperElectric
  • 17,548
  • 10
  • 52
  • 69
  • 86
    Here is a one-liner interpretation of [@mgilson's answer](http://stackoverflow.com/a/15008806/52074) `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.) – Trevor Boyd Smith Apr 27 '17 at 12:13
  • 27
    Here is a one-liner interpretation of [@Maxim's answer](http://stackoverflow.com/a/43357954/52074) `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](http://stackoverflow.com/a/18472142/52074)) – Trevor Boyd Smith Apr 27 '17 at 12:33
  • 12
    how about something like `parser.add_argument('--my_bool', action='store_true', default=False)` – AruniRC Nov 01 '17 at 19:11
  • 1
    For answer by @TrevorBoydSmith , try import with `import distutils.util` instead of `import disutils`. See [this answer](https://stackoverflow.com/questions/54667129/understanding-the-difference-between-import-x-and-from-x-y-import-z) – Osama Dar Aug 12 '21 at 12:49
  • 5
    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. – Anatoly Alekseev Dec 23 '21 at 16:50
  • @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`. – hpaulj Jan 09 '22 at 18:38

27 Answers27

1421

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?
Tahlor
  • 1,642
  • 1
  • 17
  • 21
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • 187
    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 issue](http://bugs.python.org/issue14392) – dolphin Jul 20 '13 at 01:03
  • 3
    @dolphin -- 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. – mgilson Jul 20 '13 at 05:03
  • 78
    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. – dolphin Aug 06 '13 at 12:30
  • 5
    @dolphin -- 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`. – mgilson Aug 06 '13 at 16:46
  • Read about it in the docs: http://docs.python.org/2.7/library/argparse.html#action – Buttons840 Sep 06 '13 at 00:23
  • 133
    @mgilson -- 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. – dolphin Sep 08 '13 at 21:51
  • 21
    @dolphin -- 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](https://mail.python.org/mailman/listinfo)? 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:) – mgilson Sep 09 '13 at 16:01
  • 4
    @mgilson 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. – dolphin Sep 10 '13 at 20:14
  • 24
    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`. – hpaulj Oct 07 '13 at 17:09
  • 5
    This behavior is definitely misleading: it works for str, works for int, but fails for bool ?! – Christophe Roussy Feb 15 '16 at 17:01
  • 6
    @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. – mgilson Feb 15 '16 at 17:25
  • 3
    @mgilson 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... – Christophe Roussy Feb 15 '16 at 18:01
  • 2
    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. – Matyas May 12 '16 at 01:15
  • 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. – Tom Karzes Sep 02 '16 at 05:33
  • @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 ... – mgilson Sep 02 '16 at 06:09
  • 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? – zhihong Dec 19 '16 at 10:03
  • @zhihong -- 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? – mgilson Dec 19 '16 at 16:40
  • 4
    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?? – That1Guy Jul 27 '17 at 17:25
  • 1
    @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?) – mgilson Apr 20 '18 at 14:57
  • 1
    @mgilson My comment was indeed insulting and deeply inappropriate. And not at all constructive, as you point out. I totally apologize. – Josh Burkart Apr 21 '18 at 22:57
  • 2
    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! :) – Josh Burkart Apr 21 '18 at 23:04
  • 2
    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? – SwimBikeRun Apr 22 '19 at 21:45
  • @SwimBikeRun -- 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). – mgilson Apr 23 '19 at 22:11
  • @dolphin 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`. – HelloGoodbye Oct 18 '19 at 08:47
  • 1
    @HelloGoodbye We are talking about the [Boolean type](https://docs.python.org/3/library/functions.html#bool) 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. – dolphin Nov 01 '19 at 17:32
  • @dolphin 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. – HelloGoodbye Nov 06 '19 at 16:46
  • @dolphin 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`. – HelloGoodbye Nov 06 '19 at 16:51
  • 2
    @HelloGoodbye 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. – dolphin Nov 23 '19 at 20:39
  • @dolphin 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. – HelloGoodbye Nov 24 '19 at 21:50
  • 2
    @HelloGoodbye 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. – dolphin Nov 25 '19 at 02:41
  • @dolphin 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`? – HelloGoodbye Nov 28 '19 at 23:32
  • 2
    @HelloGoodbye 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"? – dolphin Nov 29 '19 at 05:01
  • @dolphin 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? – HelloGoodbye Nov 29 '19 at 17:39
  • 2
    @HelloGoodbye 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. – dolphin Nov 29 '19 at 19:57
  • @dolphin 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. – HelloGoodbye Dec 01 '19 at 20:29
  • 2
    @dolphin 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. – HelloGoodbye Dec 01 '19 at 20:31
  • for the --no- pattern argparse introduced the BooleanOptionalAction docs.python.org/3/library/argparse.html#action – pseyfert Nov 23 '20 at 10:59
  • In addition, to use variables in `set_defaults()` use `parser.set_defaults(**{name: default})` where `name` and `default` are variables (not the actual names). – esotericpig May 04 '21 at 18:14
  • @mgilson 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. – Corey Levinson Jun 23 '21 at 15:09
  • If default value for feature is already `False`, why do we need `'--no-feature'` ? – alper Sep 02 '21 at 12:06
  • 1
    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). – mgilson Sep 02 '21 at 15:54
495

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.

dennlinger
  • 9,890
  • 1
  • 42
  • 63
Maxim
  • 7,207
  • 1
  • 30
  • 29
  • What is the need to specify nargs, the OP is asking for just one argument. Please mention if I am missing something. :) – Abhishek Bhatia Aug 03 '17 at 11:52
  • 10
    nargs='?' means zero or one argument. https://docs.python.org/3/library/argparse.html#nargs – Maxim Aug 03 '17 at 12:05
  • 1
    I love this, but my equivalent of default=NICE is giving me an error, so I must need to do something else. – Michael Mathews Sep 15 '17 at 23:42
  • 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 to – Maxim Sep 17 '17 at 16:52
  • 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. – Marcello Romani Oct 24 '17 at 10:33
  • 2
    @MarcelloRomani str2bool is not a type in the Python sense, it is the function defined above, you need to include it somewhere. – Maxim Jan 30 '18 at 10:48
  • 21
    the code of `str2bool(v)` could be replaced with `bool(distutils.util.strtobool(v))`. Source: https://stackoverflow.com/a/18472142/2436175 – Antonio Aug 17 '18 at 00:30
  • 4
    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 wrong – NutCracker Aug 23 '18 at 10:59
  • 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. – aviator Dec 12 '20 at 18:58
  • This solution is very nice, but it will produce "None", if the argument is provided without a value. Should be False in that case. – martin_wun May 06 '21 at 14:04
  • @martin_wun did you try it? The `default=False` will produce `False`, not `None` – Maxim May 07 '21 at 12:21
  • @Maxim Yes, I did. I got "None" instead of "False". Default was set to "False" alright. – martin_wun May 07 '21 at 12:26
  • @martin_wun I'm surprised, as I tested it locally and got `False`, not `None` - here is some demo code https://www.ideone.com/yaPbWp – Maxim May 08 '21 at 00:57
  • I've come here so many times that I had to give you an upvote, thank you. – Le Sir Dog May 15 '21 at 14:23
375

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')
fnkr
  • 9,428
  • 6
  • 54
  • 61
  • 2
    how does the set_default know where to save the default? – Charlie Parker Sep 14 '16 at 00:43
  • 5
    @CharlieParker `add_argument` is called with `dest='feature'`. `set_defaults` is called with `feature=True`. Understand? – fnkr Sep 14 '16 at 12:15
  • 5
    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 flags – kevlarr Mar 20 '18 at 14:23
  • 1
    @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. – cowlinator Apr 04 '18 at 01:37
  • 6
    @cowlinator Why is SO ultimately about answering "questions as stated"? According to [its own guidelines](https://stackoverflow.com/help/how-to-answer), 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. – kevlarr Apr 04 '18 at 02:45
  • 3
    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)` – Alex Che Sep 24 '18 at 15:02
  • 3
    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? – Ken Williams Dec 26 '18 at 18:49
  • 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! – David Waterworth Jan 07 '19 at 01:46
  • 1
    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`. – user1338062 May 23 '20 at 09:28
  • for the --no- pattern argparse introduced the BooleanOptionalAction docs.python.org/3/library/argparse.html#action – pseyfert Nov 23 '20 at 10:59
  • This is neat -- too bad I didn't learn about this before you posted it so I could have gotten all these upvotes ;) – mgilson Dec 08 '20 at 06:40
126

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.")
Gringo Suave
  • 29,931
  • 6
  • 88
  • 75
Schaki
  • 1,697
  • 1
  • 12
  • 14
  • 11
    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. – Garren S Aug 28 '18 at 22:01
  • 1
    Please *NEVER* use equality operator on things like bool or nonetype. You should use *IS* instead – webknjaz -- Слава Україні Mar 28 '19 at 14:19
  • 8
    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!) – Siphon May 08 '19 at 18:42
  • 9
    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. – sdbbs Nov 26 '19 at 10:04
  • A trivial note: the default default of None will generally work fine here as well. – Gringo Suave Dec 25 '20 at 01:52
  • @sdbbs Just do below python3 test.py --do-something ---> this True python3 test.py ----> This is False – Smaurya Jul 29 '22 at 11:46
88

oneliner:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Evalds Urtans
  • 6,436
  • 1
  • 41
  • 31
  • 21
    good for oneliner fan, also it could be improved a bit: `type=lambda x: (str(x).lower() in ['true','1', 'yes'])` – Tu Bui Jan 23 '19 at 13:55
  • 9
    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. – Jethro Oct 08 '20 at 10:18
  • 1
    Small correction @Jethro's comment: This should be `distutils.util.strtobool` (no `s`). Works great! – Daniel Jones Aug 04 '22 at 16:04
  • `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. – grofte Oct 10 '22 at 08:34
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.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • 3
    Or use: parser.register('type', 'bool', (lambda x: x.lower() in ("yes", "true", "t", "1"))) – Matyas May 12 '16 at 01:15
35

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.

Gringo Suave
  • 29,931
  • 6
  • 88
  • 75
Akash Desarda
  • 704
  • 8
  • 6
30

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!

dl.meteo
  • 1,658
  • 15
  • 25
  • 1
    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. – Simon O'Hanlon Jun 30 '19 at 21:28
  • 5
    While simple, it does not answer the question. OP want an argument where you can specify `--feature False` – Astariul Nov 28 '19 at 08:33
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.

susundberg
  • 650
  • 7
  • 14
  • 8
    If you're going to go this route, might I suggest `distutils.util.strtobool(v)`. – CivFan Aug 15 '17 at 16:31
  • 2
    The `distutils.util.strtobool` returns 1 or 0, not an actual boolean. – CMCDragonkai Apr 16 '19 at 02:12
  • 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. – grofte Oct 10 '22 at 08:43
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')
Stumpy Joe Pete
  • 360
  • 2
  • 12
  • 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'}`. – Jerry101 Aug 01 '18 at 09:49
14

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.

Russell
  • 1,252
  • 15
  • 21
  • 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? – edesz Apr 28 '20 at 00:49
  • 1
    `eval` is a built-in function. https://docs.python.org/3/library/functions.html#eval This can be any unary function which other, more flexible approaches take advantage. – Russell Apr 29 '20 at 01:08
  • Hey, that's great. Thanks! – edesz Apr 29 '20 at 02:02
  • 3
    that's cute, but quite risky to just put out into the wild where users who aren't aware of [eval being evil](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html) will just copy-paste it into their scripts. – Arne Jun 08 '20 at 15:07
  • 1
    @Arne, good point. Although, it appears that it would be pretty difficult for a well-intentioned user to accidentally do something pernicious. – Russell Jun 08 '20 at 17:08
  • 1
    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. – Gringo Suave Dec 25 '20 at 02:00
  • Not recommended. If users pass in some value like "no", "n", it will throw a NameError. – Loner Apr 22 '22 at 06:28
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.

foo
  • 940
  • 2
  • 9
  • 20
12

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.

Zach Rieck
  • 419
  • 1
  • 4
  • 23
  • You are mentioning this working in 3.7+ and 3.9+. Which one is it? – David Schumann Aug 23 '21 at 14:19
  • 3.7+. Clarified in an edit – Zach Rieck Nov 05 '21 at 17:50
  • 1
    The python documentation mentions `New in version 3.9` and I cannot import `BooleanOptionalAction` from `argparse` in 3.7... – David Zwicker Nov 26 '21 at 17:27
  • 9
    **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."`. – hpaulj Jan 08 '22 at 07:44
  • 7
    Agreed, this answer should not be accepted: `args = ap.parse_args(['--foo', 'False'])` returns True (and in my opinion it shouldn't – Jblasco Mar 22 '22 at 11:19
9

A simpler way would be to use as below.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
arunkumarreddy
  • 159
  • 1
  • 7
6

After previously following @akash-desarda 's excellence answer https://stackoverflow.com/a/59579733/315112 , 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'
Indra Ginanjar
  • 354
  • 1
  • 4
  • 12
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.

gerardw
  • 5,822
  • 46
  • 39
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')
FiddleStix
  • 3,016
  • 20
  • 21
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
Costa Huang
  • 1,415
  • 15
  • 15
3

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')
lalit gangwar
  • 389
  • 4
  • 10
3

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', ...].

Peng Zheng
  • 61
  • 2
2

I think the most canonical way will be:

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

ENSURE = config.ensure is None
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
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
  • 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'])` – Akanni Jan 19 '21 at 03:58
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)
Robert T. McGibbon
  • 5,075
  • 3
  • 37
  • 45
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")
Youngjae
  • 24,352
  • 18
  • 113
  • 198
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')
pc_pyr
  • 562
  • 5
  • 20
delan
  • 1
  • 4
    How is that different from [this answer](https://stackoverflow.com/a/36031646/6045800) from 4 years ago? – Tomerikoo Sep 17 '20 at 14:35
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)

scorpionipx
  • 107
  • 5
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()

reddy nishanth
  • 396
  • 6
  • 11
  • 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 BoolAction – Yaakov Saxon Mar 21 '23 at 15:58