Analisi dei valori booleani con argparse


616

Vorrei usare argparse per analizzare gli argomenti booleani della riga di comando scritti come "--foo True" o "--foo False". Per esempio:

my_program --my_boolean_flag False

Tuttavia, il seguente codice di prova non fa quello che vorrei:

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)

Purtroppo, parsed_args.my_boolvaluta True. Questo è il caso anche quando cambio cmd_linea esserlo ["--my_bool", ""], il che è sorprendente, dal momento che lo bool("")valuta False.

Come posso ottenere argparse per analizzare "False", "F"e la loro minuscole varianti di essere False?


40
Ecco un'interpretazione di una riga della risposta di @ mgilsonparser.add_argument('--feature', dest='feature', default=False, action='store_true') . Questa soluzione ti garantirà sempre un booltipo con valore Trueo False. (Questa soluzione ha un vincolo: l'opzione deve avere un valore predefinito.)
Trevor Boyd Smith,

7
Ecco un'interpretazione di una riga della risposta di @ Maximparser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Quando viene utilizzata l'opzione, questa soluzione garantirà un booltipo con valore di Trueo False. Quando l'opzione non viene utilizzata, otterrai None. ( distutils.util.strtobool(x)è da un'altra domanda di StackOverflow )
Trevor Boyd Smith,

8
che ne dici di qualcosa del genereparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Risposte:


276

Ancora un'altra soluzione che utilizza i suggerimenti precedenti, ma con l'errore di analisi "corretto" di 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.')

Questo è molto utile per creare switch con valori predefiniti; per esempio

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

mi permette di usare:

script --nice
script --nice <bool>

e utilizzare comunque un valore predefinito (specifico per le impostazioni dell'utente). Un aspetto negativo (indirettamente correlato) con questo approccio è che i 'nargs' potrebbero cogliere un argomento posizionale - vedi questa domanda correlata e questa segnalazione di bug argparse .


4
nargs = '?' significa zero o un argomento. docs.python.org/3/library/argparse.html#nargs
Massimo

1
Adoro questo, ma il mio equivalente di default = NICE mi sta dando un errore, quindi devo fare qualcos'altro.
Michael Mathews,

2
@MarcelloRomani str2bool non è un tipo in senso Python, è la funzione sopra definita, è necessario includerla da qualche parte.
Massimo

4
il codice di str2bool(v)potrebbe essere sostituito con bool(distutils.util.strtobool(v)). Fonte: stackoverflow.com/a/18472142/2436175
Antonio,

4
Forse vale la pena ricordare che in questo modo non è possibile verificare se l'argomento è impostato con if args.nice:perché se l'argomento è impostato su False, non passerà mai la condizione. Se questo è giusto, allora forse è meglio tornare lista dalla str2boolfunzione e la set list come constparametri, come questo [True], [False]. Correggimi se sbaglio
Schiaccianoci

889

Penso che un modo più canonico per farlo sia tramite:

command --feature

e

command --no-feature

argparse supporta bene questa versione:

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

Naturalmente, se vuoi davvero la --arg <True|False>versione, potresti passare ast.literal_evalcome "tipo", o una funzione definita dall'utente ...

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?

96
Penso ancora che type=booldovrebbe funzionare fuori dagli schemi (considera gli argomenti posizionali!). Anche quando si specifica ulteriormente choices=[False,True], si finisce con "Falso" e "Vero" considerato Vero (a causa di un cast da stringa a bool?). Problema forse correlato
delfino il

41
Bene, penso solo che non ci sia giustificazione per questo non funziona come previsto. E questo è estremamente fuorviante, in quanto non esistono controlli di sicurezza né messaggi di errore.
delfino

69
@mgilson - Quello che trovo fuorviante è che puoi impostare type = bool, non ricevi alcun messaggio di errore, eppure, sia per gli argomenti stringa "False" che "True", ottieni True nella tua variabile presumibilmente booleana (a causa di come il tipo casting funziona in python). Quindi o type = bool dovrebbe essere chiaramente non supportato (emettere alcuni avvisi, errori, ecc.), Oppure dovrebbe funzionare in modo utile e intuitivamente previsto.
delfino,

14
@dolphin - rispettivamente, non sono d'accordo. Penso che il comportamento sia esattamente come dovrebbe essere ed è coerente con lo zen di Python "I casi speciali non sono abbastanza speciali da infrangere le regole". Tuttavia, se lo senti fortemente, perché non farlo apparire in una delle varie mailing list di Python ? Lì, potresti avere la possibilità di convincere qualcuno che ha il potere di fare qualcosa su questo problema. Anche se tu fossi stato in grado di convincermi, saresti riuscito solo a convincermi e il comportamento non cambierà ancora poiché non sono un dev :)
mgilson

15
Stiamo discutendo su cosa bool()dovrebbe fare la funzione Python o su cosa dovrebbe accettare argparse type=fn? Tutti i argparsecontrolli fnsono richiamabili. Si aspetta fndi prendere un argomento stringa e di restituire un valore. Il comportamento di fnè responsabilità del programmatore, no argparse's.
hpaulj,

235

Raccomando la risposta di mgilson ma con un gruppo
che si esclude a vicenda in modo che non sia possibile utilizzarlo --featuree --no-featureallo stesso tempo.

command --feature

e

command --no-feature

ma no

command --feature --no-feature

script:

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)

È quindi possibile utilizzare questo aiuto se si desidera impostare molti di essi:

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

5
@CharlieParker add_argumentviene chiamato con dest='feature'. set_defaultsviene chiamato con feature=True. Capire?
fnkr

4
Questa o la risposta di mgilson avrebbe dovuto essere la risposta accettata - anche se l'OP voleva --flag False, parte delle risposte SO dovrebbe riguardare COSA stanno cercando di risolvere, non solo COME. Non ci dovrebbe essere assolutamente alcun motivo per fare --flag Falseo --other-flag Truequindi utilizzare un parser personalizzato per convertire la stringa in un valore booleano .. action='store_true'e action='store_false'sono i modi migliori per utilizzare flag booleani
kevlarr

6
@cowlinator Perché SO alla fine è rispondere alle "domande come indicato"? Secondo le sue linee guida , una risposta ... can be “don’t do that”, but it should also include “try this instead”che (almeno per me) implica risposte dovrebbe andare più in profondità quando appropriato. Ci sono sicuramente momenti in cui alcuni di noi che postano domande possono beneficiare di una guida sulle migliori / migliori pratiche, ecc. Rispondere "come dichiarato" spesso non lo fa. Detto questo, la tua frustrazione per le risposte che spesso assumono troppo (o erroneamente) è completamente valida.
kevlarr,

2
Se si desidera avere un terzo valore per quando l'utente non ha specificato esplicitamente la funzione, è necessario sostituire l'ultima riga conparser.set_defaults(feature=None)
Alex Che

2
Se vogliamo aggiungere una help=voce per questo argomento, dove dovrebbe andare? Nella add_mutually_exclusive_group()chiamata? In una o entrambe le add_argument()chiamate? Altrove?
Ken Williams,

57

Ecco un'altra variante senza riga / e aggiuntiva per impostare i valori predefiniti. A bool è sempre assegnato un valore in modo che possa essere utilizzato in istruzioni logiche senza controlli preliminari.

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")

6
Questa risposta è sottovalutata, ma meravigliosa nella sua semplicità. Non tentare di impostare, required=Truealtrimenti otterrai sempre un vero argomento.
Garren S

1
Si prega di NON utilizzare operatore di uguaglianza su cose come bool o NoneType. Si dovrebbe usare IS invece
webKnjaZ

2
Questa è una risposta migliore di quella accettata perché controlla semplicemente la presenza del flag per impostare il valore booleano, invece di richiedere una stringa booleana ridondante. (Yo dawg, ti ho sentito come un booleano ... quindi ti ho dato un booleano con il tuo booleano per impostare il tuo booleano!)
Sifone

4
Hmm ... la domanda, come detto, sembra voler usare "True" / "False" sulla riga di comando stessa; tuttavia, con questo esempio, python3 test.py --do-something Falsefallisce error: unrecognized arguments: False, quindi non risponde realmente alla domanda.
sdbbs

38

oneliner:

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

4
ottimo per i fan degli oneliner, inoltre potrebbe essere migliorato un po ':type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui,

35

Sembra esserci un po 'di confusione su cosa type=boole cosa type='bool'potrebbe significare. Uno (o entrambi) dovrebbe significare "eseguire la funzione bool()o" restituire un valore booleano "? Così com'è type='bool'non significa nulla. add_argumentfornisce un 'bool' is not callableerrore, come se fosse stato utilizzato type='foobar', oppuretype='int' .

Ma argparseha un registro che ti consente di definire parole chiave come questa. È usato principalmente per action, ad esempio `action = 'store_true'. Puoi vedere le parole chiave registrate con:

parser._registries

che visualizza un dizionario

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

Sono state definite molte azioni, ma solo un tipo, quello predefinito, argparse.identity .

Questo codice definisce una parola chiave "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()non è documentato, ma anche non nascosto. Per la maggior parte il programmatore non ha bisogno di sapere perché typee actionvalori introito di funzione e di classe. Esistono molti esempi di stackoverflow per la definizione di valori personalizzati per entrambi.


Nel caso in cui non fosse ovvio dalla discussione precedente, bool()non significa "analizzare una stringa". Dalla documentazione di Python:

bool (x): converti un valore in un valore booleano, usando la procedura standard di verifica della verità.

Contrastare questo con

int (x): converte un numero o una stringa x in un numero intero.


3
Oppure usa: parser.register ('type', 'bool', (lambda x: x.lower () in ("yes", "true", "t", "1")))
Matyas

17

Stavo cercando lo stesso problema, e immagino che la bella soluzione sia:

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

e usarlo per analizzare la stringa in booleano come suggerito sopra.


5
Se hai intenzione di seguire questa strada, potrei suggerire distutils.util.strtobool(v).
CivFan,

1
I distutils.util.strtoboolrendimenti 1 o 0, non un vero e proprio booleano.
CMCDragonkai

14

Un modo abbastanza simile è usare:

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

e se imposti l'argomento --feature nel tuo comando

 command --feature

l'argomento sarà True, se non si imposta type --feature l'argomento default è sempre False!


1
C'è qualche svantaggio di questo metodo che le altre risposte superano? Questa sembra essere di gran lunga la soluzione più semplice e concisa che arriva a ciò che l'OP (e in questo caso io) voleva. Lo adoro.
Simon O'Hanlon,

2
Sebbene semplice, non risponde alla domanda. OP vuole un argomento in cui è possibile specificare--feature False
Astariul


12

Questo funziona per tutto ciò che mi aspetto che:

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

Il codice:

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

Eccellente! Vado con questa risposta. Ho modificato il mio _str_to_bool(s)per convertire s = s.lower()una volta, quindi testare if s not in {'true', 'false', '1', '0'}e infine return s in {'true', '1'}.
Jerry101

6

Un modo più semplice sarebbe usare come di seguito.

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

5

Più semplice. Non è flessibile, ma preferisco la semplicità.

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

EDIT: se non ti fidi dell'input, non usare eval.


Questo sembra abbastanza conveniente. Ho notato che hai eval come tipo. Avevo una domanda al riguardo: come si dovrebbe definire eval o è necessaria un'importazione per poterla utilizzare?
edesz,

1
evalè una funzione integrata. docs.python.org/3/library/functions.html#eval Questa può essere qualsiasi funzione unaria che traggono vantaggio da altri approcci più flessibili.
Russell

Ehi, va benissimo. Grazie!
edesz,

2
è carino, ma abbastanza rischioso semplicemente uscire allo stato brado in cui gli utenti che non sono consapevoli del fatto che Eval sia cattivo lo copieranno e incolleranno nei loro script.
Arne,

@Arne, buon punto. Tuttavia, sembra che sarebbe piuttosto difficile per un utente ben intenzionato fare accidentalmente qualcosa di dannoso.
Russell

3

Il modo più semplice sarebbe usare le scelte :

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

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

Non passando - my-flag restituisce False. L' opzione obbligatoria = True potrebbe essere aggiunta se si desidera sempre che l'utente specifichi esplicitamente una scelta.


2

Penso che il modo più canonico sarà:

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

ENSURE = config.ensure is None

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)

1

Il modo più semplice e corretto è

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

Si noti che i valori True sono y, yes, t, true, on e 1; i valori falsi sono n, no, f, false, off e 0. Aumenta ValueError se val è qualcos'altro.


0

Facile e veloce, ma solo per argomenti 0 o 1:

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

L'output sarà "Falso" dopo aver chiamato dal terminale:

python myscript.py 0

-1

Simile a @Akash ma qui è un altro approccio che ho usato. Usa strche lambdaperché il pitone lambdami dà sempre sentimenti alieni.

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")

-1

Come miglioramento alla risposta di @Akash Desarda, potresti fare

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)

E supporta 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.