Rozważ następujący kod Python 2:

from argparse import ArgumentParser

p = ArgumentParser(prog="test")
p.add_argument('--bar')
sp = p.add_subparsers()
sp1 = sp.add_parser('foo')
sp1.add_argument('--baz')

p.parse_args(['foo', '--bar'])

Otrzymuję komunikat o błędzie:

usage: test [-h] [--bar BAR] {foo} ...
test: error: unrecognized arguments: --bar

Wydaje się, że sugeruje, że --bar jest nierozpoznanym argumentem dla test. Ale w rzeczywistości jest to nierozpoznany argument dla podkomisji foo.

Myślę, że komunikat o błędzie powinien być:

usage: test foo [-h] [--baz BAZ]
foo: error: unrecognized arguments: --bar

Czy to błąd w argpara? Czy mogę uzyskać konfigurację argparse, aby podać poprawny komunikat o błędzie?

6
augurar 16 sierpień 2014, 00:36

3 odpowiedzi

Najlepsza odpowiedź

Jeśli zmierzę swój skrypt

p = ArgumentParser(prog="test")
p.add_argument('--bar')
sp = p.add_subparsers(dest='cmd')
sp1 = sp.add_parser('foo')
sp1.add_argument('--baz')
print p.parse_known_args()

Wyjście jest

1517:~/mypy$ python2.7 stack25333847.py foo --bar
(Namespace(bar=None, baz=None, cmd='foo'), ['--bar'])

Parser p napotyka foo, jeden z dozwolonych opcji {x2}}. Więc teraz delegaci parsowanie do subparsera, sp1. sp1 nie rozpoznaje --bar, więc zwraca go do głównego parsera na liście nierozpoznanych argumentów. Domyślnym działaniem jest główny parser, aby go przekazać, jakby (ja) nie rozpoznał ciągu.

Z powodu jej pozycji po foo --bar nie jest rozpoznawany przez jednego parsera. To samo pójdzie na ['foo', '--boo'].

Delegacja do podszarowego jest wykonywana w metodzie __call__ sp (podszarowe działanie). W części brzmi:

def __call__(self, parser, namespace, values, option_string=None):
    ...
    # parse all the remaining options into the namespace
    # store any unrecognized options on the object, so that the top
    # level parser can decide what to do with them
    namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
    if arg_strings:
        vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
        getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)

Tak więc obsługa nierozpoznanych_argów pozostaje przez projekt, do głównego parsera (ten, który nazywa parse_args, a nie parse_known_args).


Inny błąd, taki jak pomijanie wartości dla --baz wygeneruje komunikat o błędzie w podszarowym:

1523:~/mypy$ python2.7 stack25333847.py foo --baz
usage: test foo [-h] [--baz BAZ]
test foo: error: argument --baz: expected one argument

Zorientowałem się na generowanie:

usage: test foo [-h] [--baz BAZ]
test foo: error: unrecognized arguments: --bar

Choć nie jest krótki i słodki. I podklasę argparse._SubParsersAction; Daj mu nowy __call__, który używa parse_args zamiast parse_known_args. Muszę też zmienić główny rejestr parsera. (Mogę dodać kod, jeśli chcesz).

3
hpaulj 15 sierpień 2014, 22:54

Niekoniecznie mówiono, że jest to błąd, ale raczej uproszczone podejście do generowania komunikatu o błędzie.

Domyślny komunikat o błędzie po prostu stwierdza "{PROG}: error: unrecognized arguments: {ALL_UNRECOGNIZED_ARGS}", niezależnie od tego, co (sub-) parser nierozpoznany argumenty są związane z.

Jeśli spojrzysz na to, jak parse_args() Generuje błąd , to dość oczywiste, że informacje te nie są łatwo dostępne:

def parse_args(self, args=None, namespace=None):
    args, argv = self.parse_known_args(args, namespace)
    if argv:
        msg = _('unrecognized arguments: %s')
        self.error(msg % ' '.join(argv))
    return args

Wywołanie {x0}} po prostu przeanalizuje znane argumenty i zwróci je jako przestrzeń nazw args i zwróci wszystkie pozostałe, nieznane argumenty jako argv.

Więc możesz bezpośrednio używać parse_known_args() sam . Możesz również zapisać nazwę podkomisji do przestrzeni nazw

sp = p.add_subparsers(dest='cmd')

A następnie uzyskaj dostęp do niego przy użyciu args.cmd, aby określić, który podkomisja użytkownik próbował wywołać.

W oparciu o to, w zależności od tego, jak prosta jest hierarchia parser, można wygenerować bardziej pomocny komunikat o błędzie (patrz parser.error()).

Ale to nie będzie idealne. Jeśli użytkownik miał określić nieznany argument zarówno dla głównego parsera, jak i podostarce, te dwa nierozpoznane argumenty pozostaną w argv i widzę znać oczywisty sposób określania, który z nich został przekazany, które polecenie linia.

1
Lukas Graf 15 sierpień 2014, 21:44

Naprawiłem problem z następującymi:

from argparse import ArgumentParser, _

class _ArgumentParser(ArgumentParser):
    # "parse_known_args" is made to behave exactly like "parse_args".
    def parse_known_args(self, args=None, namespace=None):
        args, argv = super().parse_known_args(args, namespace)
        if argv:
            msg = _('unrecognized arguments: %s')
            self.error(msg % ' '.join(argv))
        return args, argv

p = ArgumentParser(prog="test")
p.add_argument('--bar')
sp = p.add_subparsers(parser_class=_ArgumentParser)
sp1 = sp.add_parser('foo')
sp1.add_argument('--baz')

p.parse_args(['foo', '--bar'])

Otrzymuję następujący komunikat o błędzie:

usage: test foo [-h] [--baz BAZ]
test foo: error: unrecognized arguments: --bar

Które myślę, że jest dokładnie to, czego chciał, nie?

Nie mam pojęcia, dlaczego python ma tam parse_known_args. Widzę tam zero przydatność i ja twierdzę, że powinno być tam parse_args.

0
Mitar 11 maj 2020, 07:52