Analiza wartości logicznych za pomocą argparse

611

Chciałbym użyć argparse do analizy argumentów wiersza poleceń boolean zapisanych jako „--foo True” lub „--foo False”. Na przykład:

my_program --my_boolean_flag False

Jednak poniższy kod testowy nie robi tego, co chciałbym:

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)

Niestety parsed_args.my_boolocenia True. Jest tak nawet w przypadku, kiedy zmieni cmd_linesię["--my_bool", ""] , co jest zaskakujące, ponieważ bool("")evalutates do False.

Jak mogę uzyskać argparse do analizowania "False", "F"a ich małymi literami warianty być False?

SuperElectric
źródło
40
Oto jednoliniowa interpretacja odpowiedzi @ mgilsonaparser.add_argument('--feature', dest='feature', default=False, action='store_true') . To rozwiązanie zagwarantuje, że zawsze otrzymasz booltyp o wartości Truelub False. (To rozwiązanie ma ograniczenie: twoja opcja musi mieć wartość domyślną.)
Trevor Boyd Smith
7
Oto jednoznaczna interpretacja odpowiedzi @ Maxima parser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Gdy zostanie użyta opcja, to rozwiązanie zapewni booltyp o wartości Truelub False. Gdy opcja nie zostanie użyta, otrzymasz None. ( distutils.util.strtobool(x)pochodzi z innego pytania dotyczącego
Trevor Boyd Smith
8
co powiesz na cośparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Odpowiedzi:

272

Jeszcze inne rozwiązanie wykorzystujące poprzednie sugestie, ale z „poprawnym” błędem analizy z 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.')

Jest to bardzo przydatne do tworzenia przełączników z wartościami domyślnymi; na przykład

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

pozwala mi używać:

script --nice
script --nice <bool>

i nadal używaj wartości domyślnej (specyficznej dla ustawień użytkownika). Jednym (pośrednio powiązanym) minusem tego podejścia jest to, że „nargs” może złapać argument pozycyjny - zobacz to powiązane pytanie i ten raport o błędach argparse .

Maksyma
źródło
4
nargs = „?” oznacza zero lub jeden argument. docs.python.org/3/library/argparse.html#nargs
Maxim
1
Uwielbiam to, ale mój odpowiednik default = NICE daje mi błąd, więc muszę zrobić coś innego.
Michael Mathews,
2
@MarcelloRomani str2bool nie jest typem w sensie Pythona, jest to funkcja zdefiniowana powyżej, należy ją gdzieś dołączyć.
Maxim
4
kod str2bool(v)można zastąpić bool(distutils.util.strtobool(v)). Źródło: stackoverflow.com/a/18472142/2436175
Antonio
4
Być może warto wspomnieć, że w ten sposób nie można sprawdzić, czy argument jest ustawiony, if args.nice:ponieważ argument jest ustawiony na False, nigdy nie spełni warunku. Jeśli jest to prawda, to może lepiej jest wrócić z listy str2boolfunkcji i ustawić listy jako constparametru, jak ta [True], [False]. Popraw mnie, jeśli się mylę
NutCracker
887

Myślę, że bardziej kanonicznym sposobem na to jest:

command --feature

i

command --no-feature

argparse ładnie obsługuje tę wersję:

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

Oczywiście, jeśli naprawdę chcesz --arg <True|False>wersję, możesz przekazać ją ast.literal_evaljako „typ” lub funkcję zdefiniowaną przez użytkownika ...

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
źródło
96
Nadal uważam, że type=boolpowinien działać od razu (rozważ argumenty pozycyjne!). Nawet jeśli dodatkowo określisz choices=[False,True], to zarówno „Fałsz”, jak i „Prawda” są uważane za Prawda (ze względu na rzut z łańcucha na bool?). Być może związany z tym problem
delfin
41
Tak, po prostu uważam, że nie ma uzasadnienia dla tego, że nie działa zgodnie z oczekiwaniami. Jest to bardzo mylące, ponieważ nie ma kontroli bezpieczeństwa ani komunikatów o błędach.
delfin
69
@mgilson - Moim zdaniem wprowadzające w błąd jest to, że można ustawić typ = bool, nie pojawia się komunikat o błędzie, a mimo to zarówno w przypadku argumentów ciąg „False”, jak i „True” wartość True ma wartość zmienną rzekomo boolowską (ze względu na rzutowanie typu działa w Pythonie). Tak więc albo type = bool powinien być wyraźnie nieobsługiwany (emitować ostrzeżenie, błąd itp.) Lub powinien działać w sposób użyteczny i intuicyjny.
delfin
14
@dolphin - odpowiednio się nie zgadzam. Myślę, że zachowanie jest dokładnie takie, jakie powinno być i jest zgodne z zen Pythona: „Specjalne przypadki nie są na tyle wyjątkowe, by łamać reguły”. Jeśli jednak czujesz to mocno, dlaczego nie przywołać go na jednej z różnych list mailingowych Pythona ? Tam możesz przekonać kogoś, kto jest w stanie coś zrobić w tej sprawie. Nawet jeśli zdołasz mnie przekonać, uda ci się tylko mnie przekonać, a zachowanie nadal się nie zmieni, ponieważ nie jestem programistą :)
mgilson
15
Czy kłócą się o to, co Python bool() powinna zrobić funkcja , lub w co powinien się przyjąć argparse type=fn? Wszystkie argparseczeki fnsą na żądanie. Oczekuje, fnże weźmie jeden argument ciągu i zwróci wartość. Zachowanie fnjest obowiązkiem programisty, a nie argparse's.
hpaulj
235

Polecam odpowiedź mgilson, ale z ekskluzywnej grupy wzajemnie
tak, że nie można używać --featurei --no-featurew tym samym czasie.

command --feature

i

command --no-feature

ale nie

command --feature --no-feature

Scenariusz:

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)

Możesz użyć tego pomocnika, jeśli chcesz ustawić wiele z nich:

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
źródło
5
@CharlieParker add_argumentjest wywoływany z dest='feature'. set_defaultsnazywa się za pomocą feature=True. Rozumiesz?
fnkr
4
Odpowiedź ta lub mgilsona powinna być odpowiedzią zaakceptowaną - chociaż PO chciał --flag False, część odpowiedzi SO powinna dotyczyć tego, co próbują rozwiązać, a nie tylko JAK. Nie powinno być absolutnie żadnego powodu, aby zrobić --flag Falsealbo --other-flag True, a następnie użyć trochę niestandardowy parser przekonwertować ciąg na wartość logiczną .. action='store_true'i action='store_false'są najlepsze sposoby korzystania logicznych flagi
kevlarr
6
@cowlinator Dlaczego tak ostatecznie chodzi o udzielenie odpowiedzi na „pytania, jak podano”? Zgodnie z własnymi wytycznymi odpowiedź , ... can be “don’t do that”, but it should also include “try this instead”która (przynajmniej dla mnie) implikuje odpowiedzi, powinna w miarę potrzeby sięgać głębiej. Zdecydowanie są chwile, kiedy niektórzy z nas, którzy zadają pytania, mogą skorzystać z porad dotyczących lepszych / najlepszych praktyk itp. Odpowiedzi „jak stwierdzono” często tego nie robią. To powiedziawszy, twoja frustracja odpowiedziami często zakładającymi zbyt wiele (lub niepoprawnie) jest całkowicie uzasadniona.
kevlarr
2
Jeśli ktoś chce mieć trzecią wartość, gdy użytkownik nie określił wyraźnie funkcji, musi zastąpić ostatni wiersz znakiemparser.set_defaults(feature=None)
Alex Che
2
Jeśli chcemy dodać help=wpis do tego argumentu, gdzie powinien on iść? W add_mutually_exclusive_group()rozmowie? W jednym lub obu add_argument()połączeniach? Gdzieś indziej?
Ken Williams
57

Oto kolejna odmiana bez dodatkowych wierszy / s, aby ustawić wartości domyślne. Wartość bool zawsze ma przypisaną wartość, dzięki czemu można jej używać w instrukcjach logicznych bez wstępnej kontroli.

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
źródło
5
Ta odpowiedź jest niedoceniana, ale cudowna w swojej prostocie. Nie próbuj ustawiać, w required=Trueprzeciwnym razie zawsze otrzymasz True Arg.
Garren S
1
Proszę NIGDY nie używać operator równości na takie rzeczy jak bool lub nonetype. Należy użyć IS zamiast
webKnjaZ
2
Jest to lepsza odpowiedź niż zaakceptowana, ponieważ po prostu sprawdza obecność flagi w celu ustawienia wartości logicznej, zamiast wymagać zbędnego ciągu logicznego. (Yo dawg, słyszałem, że lubisz boolean ... więc dałem ci boolean z twoim booleanem, aby ustawić boolean!)
Siphon
4
Hmm ... wydaje się, że pytanie, jak powiedziano, chce użyć „True” / „False” w samym wierszu poleceń; jednak w tym przykładzie python3 test.py --do-something Falsenie udaje się error: unrecognized arguments: False, więc tak naprawdę nie odpowiada na pytanie.
sdbbs,
38

oneliner:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Evalds Urtans
źródło
4
dobre dla fanów oneliner, również można go nieco poprawić:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui
35

Wydaje się, że istnieje pewne zamieszanie co do tego type=bool i type='bool'może oznaczać. Czy jeden (lub oba) powinien oznaczać „uruchomić funkcję bool(), czy„ zwrócić wartość logiczną ”? W obecnej formie type='bool'nic nie znaczy. add_argumentdaje 'bool' is not callablebłąd, taki sam jak w przypadku użycia type='foobar', lub type='int'.

Ale argparsema rejestr, który pozwala zdefiniować takie słowa kluczowe. Jest używany głównie do action, np. `Action = 'store_true'. Możesz zobaczyć zarejestrowane słowa kluczowe za pomocą:

parser._registries

który wyświetla słownik

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

Istnieje wiele działań zdefiniowana, ale tylko jeden rodzaj, wartość domyślna, argparse.identity.

Ten kod definiuje słowo kluczowe „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()nie jest udokumentowany, ale także nie jest ukryty. W większości przypadków programista nie musi o tym wiedzieć, ponieważtype i actionfunkcja odbioru i klasy wartości. Istnieje wiele przykładów przepływu stosu definiowania niestandardowych wartości dla obu.


Jeśli nie jest to oczywiste z poprzedniej dyskusji, bool() nie oznacza to „parsowania łańcucha”. Z dokumentacji Python:

bool (x): Konwertuj wartość na wartość logiczną, używając standardowej procedury sprawdzania prawdy.

Porównaj to z

int (x): Konwertuje liczbę lub ciąg x na liczbę całkowitą.

hpaulj
źródło
3
Lub użyj: parser.register ('type', 'bool', (lambda x: x.lower () in („yes”, „true”, „t”, „1”)))
Matyas
17

Szukałem tego samego problemu, a imho ładnym rozwiązaniem jest:

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

i użycie go do parsowania łańcucha na wartość logiczną, jak sugerowano powyżej.

Susundberg
źródło
5
Jeśli zamierzasz wybrać tę trasę, proponuję distutils.util.strtobool(v).
CivFan
1
Te distutils.util.strtoboolpowroty 1 lub 0, a nie rzeczywiste logiczna.
CMCDragonkai
14

Całkiem podobny sposób polega na użyciu:

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

a jeśli ustawisz argument - funkcja w swoim poleceniu

 command --feature

argumentem będzie Prawda, jeśli nie ustawisz typu - funkcja argumentami domyślnie jest zawsze Fałsz!

dl.meteo
źródło
1
Czy istnieje jakaś wada tej metody, którą pokonują inne odpowiedzi? Wydaje się, że jest to zdecydowanie najłatwiejsze, najbardziej zwięzłe rozwiązanie, które osiąga to, czego chciał PO (aw tym przypadku ja). Kocham to.
Simon O'Hanlon
2
Choć prosty, nie odpowiada na pytanie. OP chce argumentu, w którym można określić--feature False
Astariul
12

Działa to na wszystko, czego oczekuję:

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

Kod:

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')
Kikut Joe Pete
źródło
Świetny! Idę z tą odpowiedzią. Poprawiłem mój raz, _str_to_bool(s)aby przekonwertować s = s.lower(), potem przetestować if s not in {'true', 'false', '1', '0'}i wreszcie return s in {'true', '1'}.
Jerry101
6

Prostszym sposobem byłoby użycie jak poniżej.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
arunkumarreddy
źródło
5

Najprostszy Nie jest elastyczny, ale wolę prostotę.

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

EDYCJA: Jeśli nie ufasz danych wejściowych, nie używaj eval.

Russell
źródło
To wydaje się dość wygodne. Zauważyłem, że masz typ eval. Miałem pytanie na ten temat: jak zdefiniować eval, czy też jest wymagany import, aby z niego skorzystać?
edesz
1
evaljest wbudowaną funkcją. docs.python.org/3/library/functions.html#eval Może to być dowolna jednoargumentowa funkcja, którą wykorzystują inne, bardziej elastyczne podejścia.
Russell
Hej, to wspaniale. Dzięki!
edesz
2
to urocze, ale dość ryzykowne jest po prostu wypuszczenie się w dzicz, gdzie użytkownicy, którzy nie są świadomi, że eval jest zły , po prostu skopiują go i wkleją do swoich skryptów.
Arne
@Arne, dobry punkt. Chociaż wydaje się, że dobrze intonowanym użytkownikom byłoby dość przypadkowo zrobić coś szkodliwego.
Russell
3

Najprostszym sposobem byłoby użycie opcji :

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

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

Nieprzekazanie - moja-flaga zwraca wartość False. Wymagane = True opcji można dodać, jeśli zawsze chcesz użytkownikowi podanie wyboru.

gerardw
źródło
2

Myślę, że najbardziej kanonicznym sposobem będzie:

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

ENSURE = config.ensure is None
Andreas Maertens
źródło
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
źródło
1

Najprostszym i najbardziej poprawnym sposobem jest

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

Zauważ, że wartościami True są y, tak, t, true, on i 1; fałszywe wartości to n, no, f, false, off i 0. Podnosi wartośćError, jeśli val jest czymś innym.

Akash Desarda
źródło
0

Szybko i łatwo, ale tylko dla argumentów 0 lub 1:

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

Wyjście będzie „False” po wywołaniu z terminala:

python myscript.py 0
FEMinżynier
źródło
-1

Podobne do @Akash, ale oto inne podejście, którego użyłem. Używa strniż lambdadlatego, że python lambdazawsze daje mi obce uczucia.

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
źródło
-1

Jako ulepszenie odpowiedzi @Akash Desarda możesz to zrobić

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)

I obsługuje 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
źródło