Analyser les valeurs booléennes avec argparse

616

Je voudrais utiliser argparse pour analyser les arguments de ligne de commande booléens écrits comme "--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évalue à True. C'est le cas même lorsque je change cmd_linepour être ["--my_bool", ""], ce qui est surprenant, car bool("")évalue à False.

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

SuperElectric
la source
40
Voici une interprétation à sens unique de la réponse de @ mgilsonparser.add_argument('--feature', dest='feature', default=False, action='store_true') . Cette solution vous garantira toujours un booltype avec une valeur Trueou False. (Cette solution a une contrainte: votre option doit avoir une valeur par défaut.)
Trevor Boyd Smith
7
Voici une interprétation à sens unique de la réponse de @ Maximparser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Lorsque l'option est utilisée, cette solution garantit un booltype avec la valeur Trueou False. Lorsque l'option n'est pas utilisée, vous obtiendrez None. ( distutils.util.strtobool(x)provient d'une autre question de stackoverflow )
Trevor Boyd Smith
8
que diriez-vous de quelque chose commeparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Réponses:

276

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 toujours utiliser une valeur par défaut (spécifique aux paramètres utilisateur). Un inconvénient (indirectement lié) à cette approche est que les `` nargs '' peuvent attraper un argument positionnel - voir cette question connexe et ce rapport de bug d'argparse .

Maxime
la source
4
nargs = '?' signifie zéro ou un argument. docs.python.org/3/library/argparse.html#nargs
Maxim
1
J'adore ça, mais mon équivalent de default = NICE me donne une erreur, donc je dois faire autre chose.
Michael Mathews,
2
@MarcelloRomani str2bool n'est pas un type au sens Python, c'est la fonction définie ci-dessus, vous devez l'inclure quelque part.
Maxim
4
le code de str2bool(v)pourrait être remplacé par bool(distutils.util.strtobool(v)). Source: stackoverflow.com/a/18472142/2436175
Antonio
4
Peut-être vaut-il la peine de mentionner qu'avec cette méthode, vous ne pouvez pas vérifier si l'argument est défini avec if args.nice:beacuse si l'argument est défini sur False, il ne passera jamais la condition. Si cela est vrai , alors il est peut - être préférable de retourner la liste de la str2boolfonction et la liste Définir comme constparamètre, comme celui - ci [True], [False]. Corrigez-moi si je me trompe
NutCracker
889

Je pense qu'une façon plus canonique de le faire est via:

command --feature

et

command --no-feature

argparse prend bien en charge 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 --arg <True|False>version, vous pouvez passer ast.literal_evalcomme "type", ou 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?
mgilson
la source
96
Je pense toujours que cela type=booldevrait fonctionner hors de la boîte (considérez les arguments positionnels!). Même lorsque vous spécifiez en outre choices=[False,True], vous vous retrouvez avec à la fois "Faux" et "Vrai" considéré comme Vrai (en raison d'une conversion de chaîne en booléen?). Peut-être un problème connexe
dauphin
41
Bon, je pense juste qu'il n'y a aucune justification pour que cela ne fonctionne pas comme prévu. Et cela est extrêmement trompeur, car il n'y a pas de contrôles de sécurité ni de messages d'erreur.
dauphin du
69
@mgilson - Ce que je trouve trompeur, c'est que vous pouvez définir type = bool, vous n'obtenez aucun message d'erreur, et pourtant, pour les arguments de chaîne "False" et "True", vous obtenez True dans votre variable supposée booléenne (en raison de la façon dont le casting de type fonctionne en python). Donc, soit type = bool doit être clairement non pris en charge (émettre un avertissement, une erreur, etc.), soit il doit fonctionner d'une manière utile et intuitivement attendue.
dauphin le
14
@dolphin - respectivement, je ne suis pas d'accord. Je pense que le comportement est exactement ce qu'il devrait être et est cohérent avec le zen du python "Les cas spéciaux ne sont pas assez spéciaux pour enfreindre les règles". Cependant, si cela vous tient à cœur, pourquoi ne pas l'afficher sur l'une des différentes listes de diffusion python ? Là, vous pourriez avoir une chance de convaincre quelqu'un qui a le pouvoir de faire quelque chose à ce sujet. Même si vous avez réussi à me convaincre, vous n'aurez réussi qu'à me convaincre et le comportement ne changera toujours pas puisque je ne suis pas un dev :)
mgilson
15
Sommes-nous en train de discuter de ce que la bool()fonction Python devrait faire ou de ce que argparse devrait accepter type=fn? Toutes les argparsevérifications fnsont appelables. Il s'attend fnà prendre un argument de chaîne et à renvoyer une valeur. Le comportement de fnincombe au programmeur, non argparse's.
hpaulj
235

Je recommande la réponse de mgilson mais avec un groupe mutuellement exclusif
afin que vous ne puissiez pas utiliser --featureet --no-featureen 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 cet assistant si vous allez 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')
fnkr
la source
5
@CharlieParker add_argumentest appelé avec dest='feature'. set_defaultsest appelé avec feature=True. Comprendre?
fnkr
4
Cette réponse ou celle de mgilson aurait dû être la réponse acceptée - même si le PO le voulait --flag False, une partie des réponses des SO devrait porter sur CE QU'elles essaient de résoudre, pas seulement sur COMMENT. Il ne devrait y avoir aucune raison de faire --flag Falseou d' --other-flag Trueutiliser un analyseur personnalisé pour convertir la chaîne en booléen .. action='store_true'et action='store_false'sont les meilleures façons d'utiliser les drapeaux booléens
kevlarr
6
@cowlinator Pourquoi est-ce que SO finit par répondre aux "questions comme indiqué"? Selon ses propres directives , une réponse ... can be “don’t do that”, but it should also include “try this instead”qui (au moins pour moi) implique des réponses devrait être plus approfondie le cas échéant. Il y a certainement des moments où certains d'entre nous qui posent des questions peuvent bénéficier de conseils sur les meilleures / meilleures pratiques, etc. Répondre souvent "comme indiqué" ne fait souvent pas cela. Cela étant dit, votre frustration avec des réponses supposant souvent trop (ou incorrectement) est tout à fait valable.
kevlarr
2
Si l'on veut avoir une troisième valeur pour quand l'utilisateur n'a pas spécifié explicitement de fonctionnalité, il doit remplacer la dernière ligne parparser.set_defaults(feature=None)
Alex Che
2
Si nous voulons ajouter une help=entrée pour cet argument, où devrait-il aller? Dans l' add_mutually_exclusive_group()appel? Dans l'un ou les deux add_argument()appels? Ailleurs?
Ken Williams
57

Voici une autre variante sans ligne / s supplémentaire pour définir les valeurs par défaut. Le booléen a toujours une valeur assignée afin qu'il puisse être utilisé dans des instructions logiques sans pré-vérifications.

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("Check that args.do_something=" + str(args.do_something) + " is always a bool")
Schaki
la source
6
Cette réponse est sous-estimée, mais merveilleuse dans sa simplicité. N'essayez pas de définir, required=Truesinon vous obtiendrez toujours un argument True.
Garren S
1
Veuillez NE JAMAIS utiliser d'opérateur d'égalité sur des choses comme bool ou nonetype. Vous devriez utiliser IS à la place
webKnjaZ
2
C'est une meilleure réponse que celle acceptée car elle vérifie simplement la présence de l'indicateur pour définir la valeur booléenne, au lieu d'exiger une chaîne booléenne redondante. (Yo dawg, je vous ai entendu comme des booléens ... alors je vous ai donné un booléen avec votre booléen pour définir votre booléen!)
Siphon
4
Hmm ... la question, comme indiqué, semble vouloir utiliser "True" / "False" sur la ligne de commande elle-même; cependant avec cet exemple, python3 test.py --do-something Falseéchoue avec error: unrecognized arguments: False, donc il ne répond pas vraiment à la question.
sdbbs
38

bon mot:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Evalds Urtans
la source
4
bon pour les fans d'oneliner, il pourrait aussi être amélioré un peu:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui
35

Il semble y avoir une certaine confusion quant à ce que type=boolet type='bool'pourrait signifier. Doit-on (ou les deux) signifier 'exécuter la fonction bool(), ou' retourner un booléen '? En l'état, cela type='bool'ne veut rien dire. add_argumentdonne une 'bool' is not callableerreur, comme si vous l'avez utilisé type='foobar', ou type='int'.

Mais argparsea 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, la valeur par défaut un, 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 non plus caché. Pour la plupart, le programmeur n'a pas besoin de le savoir car typeil actionprend des valeurs de fonction et de classe. Il existe de nombreux exemples de stackoverflow de définition de valeurs personnalisées pour les deux.


Dans le cas où cela n'est pas évident à partir de la discussion précédente, bool()cela ne signifie pas «analyser une chaîne». Dans la documentation Python:

bool (x): convertit une valeur en booléen, en utilisant la procédure standard de test de vérité.

Comparez cela avec

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

hpaulj
la source
3
Ou utilisez: parser.register ('type', 'bool', (lambda x: x.lower () in ("yes", "true", "t", "1")))
Matyas
17

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

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

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

susundberg
la source
5
Si vous allez suivre cette voie, je vous suggère distutils.util.strtobool(v).
CivFan
1
Le distutils.util.strtoboolretourne 1 ou 0, pas un booléen réel.
CMCDragonkai
14

Une manière 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 type --feature les arguments par défaut sont toujours False!

dl.meteo
la source
1
Y a-t-il un inconvénient à cette méthode que les autres réponses surmontent? Cela semble être de loin la solution la plus simple et la plus succincte qui parvienne à ce que l'OP (et dans ce cas moi) voulait. J'aime cela.
Simon O'Hanlon
2
Bien que simple, il ne répond pas à la question. OP veut un argument où vous pouvez spécifier--feature False
Astariul
12

Cela fonctionne pour tout ce que j'attends:

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')
Stumpy Joe Pete
la source
Excellent! Je vais avec cette réponse. J'ai peaufiné mon _str_to_bool(s)pour convertir s = s.lower()une fois, puis tester if s not in {'true', 'false', '1', '0'}et enfin return s in {'true', '1'}.
Jerry101
6

Une manière plus simple serait d'utiliser comme ci-dessous.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
arunkumarreddy
la source
5

Le plus simple. Ce n'est pas flexible, mais je préfère la simplicité.

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

EDIT: Si vous ne faites pas confiance à l'entrée, ne l'utilisez pas eval.

Russell
la source
Cela semble assez pratique. J'ai remarqué que vous avez eval comme type. J'avais une question à ce sujet: comment définir eval, ou y a-t-il une importation requise pour l'utiliser?
edesz
1
evalest une fonction intégrée. docs.python.org/3/library/functions.html#eval Il peut s'agir de n'importe quelle fonction unaire dont profitent d'autres approches plus flexibles.
Russell
Hé, c'est super. Merci!
edesz
2
c'est mignon, mais assez risqué de simplement sortir dans la nature où les utilisateurs qui ne sont pas conscients du fait qu'eval est mauvais le copieront simplement dans leurs scripts.
Arne
@Arne, bon point. Cependant, il semble qu'il serait assez difficile pour un utilisateur bien intentionné de faire accidentellement quelque chose de pernicieux.
Russell
3

Le moyen le plus simple serait d'utiliser des choix :

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

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

Ne passe pas --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.

gerardw
la source
2

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

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

ENSURE = config.ensure is None
Andreas Maertens
la source
1
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
la source
1

La manière la plus simple et la plus correcte est

from distutils import util
arser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x)))

Notez que les valeurs vraies sont y, oui, t, true, on et 1; les fausses valeurs sont n, non, f, faux, désactivé et 0. Déclenche ValueError si val est autre chose.

Akash Desarda
la source
0

Rapide et facile, mais uniquement pour les arguments 0 ou 1:

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

La sortie sera "False" après avoir appelé depuis le terminal:

python myscript.py 0
FEMengineer
la source
-1

Similaire à @Akash mais voici une autre approche que j'ai utilisée. Il utilise strque lambdaparce que le python lambdame donne toujours des sentiments extraterrestres.

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
la source
-1

Pour améliorer la réponse de @Akash Desarda, vous pourriez faire

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)

Et il prend en charge 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
la source