web-dev-qa-db-fra.com

Analyser les valeurs booléennes avec argparse

J'aimerais utiliser argparse pour analyser des arguments de ligne de commande booléens écrits sous la forme "--foo True" ou "--foo False". Par exemple:

my_program --my_boolean_flag False

Cependant, le code de test suivant ne fait pas ce que je voudrais:

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)

Malheureusement, parsed_args.my_bool est évalué à True. C'est le cas même lorsque je modifie cmd_line en ["--my_bool", ""], ce qui est surprenant, car bool("") est évalué à False.

Comment puis-je obtenir argparse pour analyser "False", "F", et leurs variantes minuscules pour être False?

497
SuperElectric

Encore une autre solution utilisant les suggestions précédentes, mais avec l'erreur d'analyse "correcte" de 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.')

Ceci est très utile pour effectuer des commutations avec des valeurs par défaut. par exemple

parser.add_argument("--Nice", type=str2bool, nargs='?',
                        const=True, default=False,
                        help="Activate Nice mode.")

me permet d'utiliser:

script --Nice
script --Nice <bool>

et utilisez toujours une valeur par défaut (spécifique aux paramètres de l'utilisateur). Un inconvénient (indirectement lié) à cette approche est que les "nargos" peuvent attraper un argument de position - voir cette question connexe et ce rapport de bogue argparse .

192
Maxim

Je pense qu'un moyen plus canonique de faire cela est via:

command --feature

et

command --no-feature

argparse supporte joliment cette version:

parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Bien sûr, si vous voulez vraiment la version --arg <True|False>, vous pouvez passer ast.literal_eval comme "type" ou une fonction définie par l'utilisateur ...

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?
745
mgilson

Je recommande la réponse de mgilson mais avec un groupe mutuellement exclusif
afin que vous ne puissiez pas utiliser --feature et --no-feature en même temps.

command --feature

et

command --no-feature

mais non

command --feature --no-feature

Scénario:

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)

Vous pouvez ensuite utiliser cette aide si vous souhaitez en définir plusieurs:

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')
202
fnkr

Il semble y avoir une certaine confusion quant à ce que pourraient signifier type=bool et type='bool'. Un (ou les deux) doit-il signifier "exécuter la fonction bool(), ou" retourner un booléen "? En l'état, type='bool' ne veut rien dire. add_argument génère une erreur 'bool' is not callable, comme si vous utilisiez type='foobar' ou type='int'.

Mais argparse a un registre qui vous permet de définir des mots clés comme celui-ci. Il est principalement utilisé pour action, par exemple. `action = 'store_true'. Vous pouvez voir les mots-clés enregistrés avec:

parser._registries

qui affiche un dictionnaire

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

Il y a beaucoup d'actions définies, mais un seul type, celui par défaut, argparse.identity.

Ce code définit un mot clé 'bool':

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() n'est pas documenté, mais n'est pas caché. Pour la plupart, le programmeur n'a pas besoin de le savoir car type et action prennent des valeurs de fonction et de classe. Il existe de nombreux exemples d'empilement de pile permettant de définir des valeurs personnalisées pour les deux.


Au cas où cela ne serait pas évident dans la discussion précédente, bool() ne signifie pas "analyser une chaîne". De la documentation Python:

bool (x): convertit une valeur en booléen à l'aide de la procédure de test de vérité standard.

Cela contraste avec

int (x): Convertit un nombre ou une chaîne x en un entier.

32
hpaulj

bon mot:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
29
Evalds Urtans

Voici une autre variante sans rangée/s supplémentaire pour définir les valeurs par défaut. La valeur booléenne a toujours une valeur attribuée pour pouvoir être utilisée dans des instructions logiques sans pré-vérification.

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 == True:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) + " is always a bool")
25
Schaki

Je cherchais le même problème, et à mon avis, la jolie solution est la suivante:

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

et en utilisant cela pour analyser la chaîne booléenne comme suggéré ci-dessus.

18
susundberg

En plus de ce que @mgilson a dit, il convient de noter qu’il existe également une méthode ArgumentParser.add_mutually_exclusive_group(required=False) qui rendrait trivial l’application du fait que --flag et --no-flag ne sont pas utilisés à la en même temps.

13
foo

Une méthode assez similaire consiste à utiliser:

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

et si vous définissez l'argument --feature dans votre commande

 command --feature

l'argument sera True, si vous ne définissez pas le type --feature, la valeur par défaut des arguments est toujours False!

11
dl.meteo

Cela fonctionne pour tout ce que je pense:

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

Le 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')
8
Stumpy Joe Pete

Un moyen plus simple serait d'utiliser comme ci-dessous.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
5
arunkumarreddy
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)
2
Robert T. McGibbon

Le moyen le plus simple serait d'utiliser choix:

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

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

Ne pas transmettre --my-flag est évalué à False. L'option required = True peut être ajoutée si vous souhaitez toujours que l'utilisateur spécifie explicitement un choix.

2
gerardw

Je pense que la manière la plus canonique sera:

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

ENSURE = config.ensure is None
2
Andreas Maertens