8

I am trying to make a build script like this:

import glob
import os
import subprocess
import re
import argparse
import shutil

def create_parser():
    parser = argparse.ArgumentParser(description='Build project')

    parser.add_argument('--clean_logs', type=bool, default=True,
                        help='If true, old debug logs will be deleted.')

    parser.add_argument('--run', type=bool, default=True,
                        help="If true, executable will run after compilation.")

    parser.add_argument('--clean_build', type=bool, default=False,
                        help="If true, all generated files will be deleted and the"
                        " directory will be reset to a pristine condition.")

    return parser.parse_args()


def main():
    parser = create_parser()
    print(parser)

However no matter how I try to pass the argument I only get the default values. I always get Namespace(clean_build=False, clean_logs=True, run=True).

I have tried:

python3 build.py --run False
python3 build.py --run=FALSE
python3 build.py --run FALSE
python3 build.py --run=False
python3 build.py --run false
python3 build.py --run 'False'

It's always the same thing. What am I missing?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Makogan
  • 8,208
  • 7
  • 44
  • 112
  • 2
    We use `action="store_true"` to deal with this kind of problem: https://docs.python.org/3.8/library/argparse.html#action – Sraw Apr 02 '20 at 19:47
  • Does this answer your question? [Parsing boolean values with argparse](https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse) – Idea O. Apr 02 '20 at 19:52
  • @IdeaO. No that answer does not explain why arparse isn;t parsing the input. My question is different. – Makogan Apr 02 '20 at 19:55
  • 3
    Try `bool("False")` or `bool("FALSE")`. The only string that returns `False` is `bool("")`. You have to write your own function that recognizes strings like 'False' or 'No' as `False`. The builtin `bool` does not do that for you. – hpaulj Apr 02 '20 at 20:01
  • An old answer: https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse – hpaulj Apr 02 '20 at 20:10

2 Answers2

15

You are misunderstanding how the argparse understands the boolean arguments.

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

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

will cause:

python3 command -x

to have x set to True and

python3 command

to have x set to False.

While action=store_false will do the opposite.


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

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

Idea O.
  • 420
  • 4
  • 10
  • A more recent bug/issue https://bugs.python.org/issue37564 found that distutils.util.strtobool can parse a variety of yes/no (English only) words to True/False values. – hpaulj Apr 06 '20 at 20:48
  • 2
    I had to remove the "type" argument to make it work. Like this: ```parser.add_argument("-Var1", action="store_true")``` since ```parser.add_argument("-Var1", type=bool, action="store_true")``` caused an error. – Manuel Popp Jun 26 '22 at 13:11
4

Argument run is initialized to True even that you pass --run False

Below code based on this great answer is workaround around this issue:

import 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.')

def main():
    ap = argparse.ArgumentParser()
    # List of args
    ap.add_argument('--foo', type=str2bool, help='Some helpful text')
    # Importable object
    args = ap.parse_args()
    print(args.foo)


if __name__ == '__main__':
    main()
rok
  • 9,403
  • 17
  • 70
  • 126