Analysieren von booleschen Werten mit argparse

614

Ich möchte argparse verwenden, um boolesche Befehlszeilenargumente zu analysieren, die als "--foo True" oder "--foo False" geschrieben sind. Zum Beispiel:

my_program --my_boolean_flag False

Der folgende Testcode macht jedoch nicht das, was ich möchte:

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)

Leider parsed_args.my_boolbewertet zu True. Dies ist auch dann der Fall, wenn ich mich cmd_linezu bin ["--my_bool", ""], was überraschend ist, da es sich um eine bool("")Bewertung handelt False.

Wie kann ich argparse zu analysieren "False", "F"und ihre unteren Gehäusevarianten zu sein False?

SuperElectric
quelle
40
Hier ist eine einzeilige Interpretation der Antwort von @ mgilsonparser.add_argument('--feature', dest='feature', default=False, action='store_true') . Diese Lösung garantiert, dass Sie immer einen boolTyp mit Wert Trueoder erhalten False. (Diese Lösung hat eine Einschränkung: Ihre Option muss einen Standardwert haben.)
Trevor Boyd Smith
7
Hier ist eine einzeilige Interpretation der Antwort von @ Maximparser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Wenn die Option verwendet wird, stellt diese Lösung einen boolTyp mit dem Wert von Trueoder sicher False. Wenn die Option nicht verwendet wird, erhalten Sie None. ( distutils.util.strtobool(x)ist aus einer anderen Stackoverflow-Frage )
Trevor Boyd Smith
8
Wie wäre es mit so etwas wieparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Antworten:

275

Noch eine andere Lösung unter Verwendung der vorherigen Vorschläge, jedoch mit dem "richtigen" Analysefehler von 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.')

Dies ist sehr nützlich, um Schalter mit Standardwerten zu erstellen. zum Beispiel

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

erlaubt mir zu verwenden:

script --nice
script --nice <bool>

und verwenden Sie weiterhin einen Standardwert (spezifisch für die Benutzereinstellungen). Ein (indirekt verwandter) Nachteil dieses Ansatzes ist, dass die 'Nargs' möglicherweise ein Positionsargument auffangen - siehe diese verwandte Frage und diesen Argparse-Fehlerbericht .

Maxime
quelle
4
nargs = '?' bedeutet null oder ein Argument. docs.python.org/3/library/argparse.html#nargs
Maxim
1
Ich liebe das, aber mein Äquivalent von default = NICE gibt mir einen Fehler, also muss ich etwas anderes tun.
Michael Mathews
2
@MarcelloRomani str2bool ist kein Typ im Python-Sinne, es ist die oben definierte Funktion, Sie müssen sie irgendwo einfügen.
Maxim
4
Der Code von str2bool(v)könnte durch ersetzt werden bool(distutils.util.strtobool(v)). Quelle: stackoverflow.com/a/18472142/2436175
Antonio
4
Vielleicht ist es erwähnenswert, dass Sie auf diese Weise nicht überprüfen können, ob das Argument mit if args.nice:beacuse gesetzt ist, wenn das Argument auf False gesetzt ist, es die Bedingung niemals bestehen wird. Wenn dieses Recht dann ist vielleicht ist es besser , Liste zurückzukehren aus str2boolFunktion und Setlist als constParameter, wie diese [True], [False]. Korrigieren Sie mich, wenn ich falsch
liege
887

Ich denke, ein kanonischerer Weg, dies zu tun, ist über:

command --feature

und

command --no-feature

argparse unterstützt diese Version gut:

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

Wenn Sie die --arg <True|False>Version wirklich wollen , können Sie sie natürlich ast.literal_evalals "Typ" oder als benutzerdefinierte Funktion übergeben ...

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
quelle
96
Ich denke immer noch, dass es type=boolsofort funktionieren sollte (betrachten Sie Positionsargumente!). Selbst wenn Sie zusätzlich angeben choices=[False,True], werden sowohl "False" als auch "True" als "True" eingestuft (aufgrund einer Umwandlung von "String" in "Bool"?). Vielleicht verwandtes Problem
Delphin
41
Richtig, ich denke nur, dass es keine Rechtfertigung dafür gibt, dass dies nicht wie erwartet funktioniert. Dies ist äußerst irreführend, da weder Sicherheitsüberprüfungen noch Fehlermeldungen vorliegen.
Delphin
69
@mgilson - Was finde ich irreführend ist , dass Sie können Typ festgelegt = Bool, haben Sie keine Fehlermeldung erhalten, und doch für beide „False“ und „True“ String - Argumente, die Sie in Ihrem angeblich boolean Variable Wahr bekommen (wegen , wie Typ Casting funktioniert in Python). Entweder sollte type = bool eindeutig nicht unterstützt werden (Warnung, Fehler usw. ausgeben), oder es sollte auf eine Weise funktionieren, die nützlich und intuitiv zu erwarten ist.
Delphin
14
@ Delphin - ich bin nicht einverstanden. Ich denke, dass das Verhalten genau so ist, wie es sein sollte und mit dem Zen von Python übereinstimmt. "Sonderfälle sind nicht speziell genug, um die Regeln zu brechen." Wenn Sie dies jedoch stark empfinden, können Sie es auf einer der verschiedenen Python- Mailinglisten erwähnen . Dort haben Sie möglicherweise die Möglichkeit, jemanden zu überzeugen, der die Macht hat, etwas gegen dieses Problem zu unternehmen . Selbst wenn Sie mich überzeugen konnten, haben Sie es nur geschafft, mich zu überzeugen, und das Verhalten wird sich immer noch nicht ändern, da ich kein
Entwickler
15
Streiten wir darüber, was die Python- bool()Funktion tun soll oder was Argparse akzeptieren soll type=fn? Alle argparsePrüfungen fnsind aufrufbar. Es wird erwartet fn, dass ein Zeichenfolgenargument verwendet und ein Wert zurückgegeben wird. Das Verhalten von fnliegt in der Verantwortung des Programmierers, nicht argparse's.
Hpaulj
235

Ich empfehle mgilson Antwort aber mit einer unvereinbaren Gruppe ,
so dass Sie nicht verwenden können , --featureund --no-featurezur gleichen Zeit.

command --feature

und

command --no-feature

aber nicht

command --feature --no-feature

Skript:

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)

Sie können diesen Helfer dann verwenden, wenn Sie viele davon festlegen möchten:

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
quelle
5
@CharlieParker add_argumentwird mit aufgerufen dest='feature'. set_defaultsheißt mit feature=True. Verstehen?
fnkr
4
Diese oder die Antwort von mgilson hätte die akzeptierte Antwort sein sollen - obwohl das OP dies wünschte --flag False, sollte ein Teil der SO-Antworten sich darauf beziehen, WAS sie zu lösen versuchen, und nicht nur darauf, wie. Es sollte überhaupt kein Grund zu tun , --flag Falseoder --other-flag Trueund dann einige benutzerdefinierten Parser verwenden , um die Zeichenfolge in einen boolean zu konvertieren .. action='store_true'und action='store_false'sind die besten Möglichkeiten boolean Fahnen zu verwenden
Kevlar
6
@cowlinator Warum geht es bei SO letztendlich darum, "Fragen wie angegeben" zu beantworten? Nach eigenen Richtlinien sollte eine Antwort, ... can be “don’t do that”, but it should also include “try this instead”die (zumindest für mich) Antworten impliziert, bei Bedarf tiefer gehen. Es gibt definitiv Zeiten, in denen einige von uns, die Fragen stellen, von Anleitungen zu besseren / besten Praktiken usw. profitieren können. Eine Antwort "wie angegeben" tut dies oft nicht. Abgesehen davon ist Ihre Frustration über Antworten, die oft von zu viel (oder falsch) ausgehen, völlig gültig.
Kevlarr
2
Wenn man einen dritten Wert haben möchte, wenn der Benutzer die Funktion nicht explizit angegeben hat, muss er die letzte Zeile durch dieparser.set_defaults(feature=None)
Alex Che
2
Wenn wir einen help=Eintrag für dieses Argument hinzufügen möchten , wohin soll es gehen? In dem add_mutually_exclusive_group()Anruf? In einem oder beiden add_argument()Anrufen? Irgendwo anders?
Ken Williams
57

Hier ist eine weitere Variante ohne zusätzliche Zeile (n) zum Festlegen von Standardwerten. Dem Bool ist immer ein Wert zugewiesen, damit er ohne Vorabprüfung in logischen Anweisungen verwendet werden kann.

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
quelle
5
Diese Antwort wird unterschätzt, ist aber in ihrer Einfachheit wunderbar. Versuchen Sie nicht zu setzen, required=Truesonst erhalten Sie immer ein True Arg.
Garren S
1
Bitte verwenden Sie NIEMALS den Gleichheitsoperator für Dinge wie bool oder nonetype. Sie sollten stattdessen IS verwenden
webKnjaZ
2
Dies ist eine bessere Antwort als die akzeptierte, da lediglich das Vorhandensein des Flags überprüft wird, um den Booleschen Wert festzulegen, anstatt eine redundante Boolesche Zeichenfolge zu erfordern. (Yo dawg, ich habe gehört, dass du Boolesche Werte magst ... also habe ich dir einen Booleschen Wert mit deinem Booleschen Wert gegeben, um deinen Booleschen Wert festzulegen!)
Siphon
4
Hmm ... die Frage scheint, wie gesagt, "True" / "False" in der Kommandozeile selbst verwenden zu wollen; Wenn dieses Beispiel jedoch python3 test.py --do-something Falsefehlschlägt error: unrecognized arguments: False, wird die Frage nicht wirklich beantwortet.
SDBBS
38

Einzeiler:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Evalds Urtans
quelle
4
gut für Oneliner-Fans, auch könnte es ein bisschen verbessert werden:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui
35

Es scheint einige Verwirrung darüber zu geben, was type=boolund type='bool'was bedeuten könnte. Sollte einer (oder beide) bedeuten, dass die Funktion ausgeführt bool()oder ein Boolescher Wert zurückgegeben wird? Wie es steht, type='bool'bedeutet nichts. add_argumentgibt einen 'bool' is not callableFehler aus, als ob Sie verwendet hätten type='foobar', oder type='int'.

Hat argparseaber eine Registrierung, mit der Sie Schlüsselwörter wie diese definieren können. Es wird meistens verwendet für actionzB `action = 'store_true'. Sie können die registrierten Schlüsselwörter sehen mit:

parser._registries

welches ein Wörterbuch anzeigt

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

Es sind viele Aktionen definiert, aber nur ein Typ, der Standardtyp argparse.identity.

Dieser Code definiert ein 'bool'-Schlüsselwort:

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()ist nicht dokumentiert, aber auch nicht versteckt. Zum größten Teil muss der Programmierer nicht darüber Bescheid wissen typeund actionFunktions- und Klassenwerte annehmen. Es gibt viele Beispiele für Stapelüberläufe zum Definieren benutzerdefinierter Werte für beide.


Falls dies aus der vorherigen Diskussion nicht ersichtlich ist, bool()bedeutet dies nicht "Parse a String". Aus der Python-Dokumentation:

bool (x): Konvertiert einen Wert in einen Booleschen Wert unter Verwendung des Standardverfahrens zum Testen der Wahrheit.

Vergleichen Sie dies mit

int (x): Konvertiert eine Zahl oder Zeichenfolge x in eine Ganzzahl.

hpaulj
quelle
3
Oder verwenden Sie: parser.register ('type', 'bool', (lambda x: x.lower () in ("yes", "true", "t", "1"))
Matyas
17

Ich habe nach dem gleichen Problem gesucht und imho ist die hübsche Lösung:

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

und damit den String wie oben vorgeschlagen in einen Booleschen Wert analysieren.

Susundberg
quelle
5
Wenn Sie diesen Weg gehen, könnte ich vorschlagen distutils.util.strtobool(v).
CivFan
1
Die distutils.util.strtoboolRückgabe 1 oder 0 ist kein tatsächlicher Boolescher Wert.
CMCDragonkai
14

Ein ganz ähnlicher Weg ist zu verwenden:

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

und wenn Sie das Argument --feature in Ihrem Befehl setzen

 command --feature

Das Argument ist True, wenn Sie den Typ --feature nicht festlegen. Die Standardeinstellung für Argumente ist immer False!

dl.meteo
quelle
1
Gibt es einen Nachteil dieser Methode, den die anderen Antworten überwinden? Dies scheint bei weitem die einfachste und prägnanteste Lösung zu sein, die das erreicht, was das OP (und in diesem Fall ich) wollte. Ich liebe es.
Simon O'Hanlon
2
Es ist zwar einfach, beantwortet aber die Frage nicht. OP wollen ein Argument, wo Sie angeben können--feature False
Astariul
12

Dies funktioniert für alles, was ich erwarte:

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

Der 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
quelle
Ausgezeichnet! Ich gehe mit dieser Antwort. Ich habe meine angepasst _str_to_bool(s), um sie s = s.lower()einmal zu konvertieren , dann zu testen if s not in {'true', 'false', '1', '0'}und schließlich return s in {'true', '1'}.
Jerry101
6

Ein einfacherer Weg wäre, wie unten zu verwenden.

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

Am einfachsten. Es ist nicht flexibel, aber ich bevorzuge Einfachheit.

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

BEARBEITEN: Wenn Sie der Eingabe nicht vertrauen, verwenden Sie sie nicht eval.

Russell
quelle
Dies scheint recht praktisch zu sein. Mir ist aufgefallen, dass Sie als Typ eval haben. Ich hatte eine Frage dazu: Wie sollte eval definiert werden, oder ist ein Import erforderlich, um davon Gebrauch zu machen?
Edesz
1
evalist eine eingebaute Funktion. docs.python.org/3/library/functions.html#eval Dies kann jede unäre Funktion sein, die andere, flexiblere Ansätze nutzen.
Russell
Hey, das ist großartig. Vielen Dank!
Edesz
2
Das ist niedlich, aber ziemlich riskant, wenn man es einfach in die Wildnis bringt, wo Benutzer, die sich nicht bewusst sind, dass es böse ist, es einfach kopieren und in ihre Skripte einfügen.
Arne vor
@Arne, guter Punkt. Es scheint jedoch, dass es für einen gut gemeinten Benutzer ziemlich schwierig wäre, versehentlich etwas Schädliches zu tun.
Russell vor
3

Der einfachste Weg wäre, Entscheidungen zu treffen :

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

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

Nicht übergeben --my-flag wird als False ausgewertet. Die erforderliche = True- Option kann hinzugefügt werden, wenn der Benutzer immer explizit eine Auswahl angeben soll.

gerardw
quelle
2

Ich denke, der kanonischste Weg wird sein:

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

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

Der einfachste und korrekteste Weg ist

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

Beachten Sie, dass True-Werte y, yes, t, true, on und 1 sind. falsche Werte sind n, no, f, false, off und 0. Löst ValueError aus, wenn val etwas anderes ist.

Akash Desarda
quelle
0

Schnell und einfach, aber nur für Argumente 0 oder 1:

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

Die Ausgabe ist nach dem Aufruf vom Terminal "False":

python myscript.py 0
FEMengineer
quelle
-1

Ähnlich wie @Akash, aber hier ist ein anderer Ansatz, den ich verwendet habe. Es verwendet strals, lambdaweil Python lambdamir immer ein fremdes Gefühl gibt.

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
quelle
-1

Als Verbesserung der Antwort von @Akash Desarda könnten Sie dies tun

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)

Und es unterstützt 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
quelle