Krótka odpowiedź brzmi, że parse_args
używa parse_known_args
. Ta metoda umożliwia obsługę nieznanych argumentów, takich jak --gold
. W wyniku tego błędy typu argumentu zostają podniesione przed błędami unknown arguments
.
Dodałem rozwiązanie obejmujące podklasę ArgumentParser
i modyfikując metodę głęboko w stosie wywołań.
Spróbuję nakreślić parse_args
zgodnie z przykładem.
Pierwszą rzeczą, którą robi to jest klasyfikowanie ciągów jako O
lub A
. Mówiąc prościej, te, które zaczynają się od -
są O
, inne A
. Próbuje również dopasować wartości O
do zdefiniowanego argumentu.
W tym przykładzie znajduje on OAA
. Regex służy do dopasowania tego ciągu do wzorów zdefiniowanych przez argument nargs
. (w razie potrzeby mogę wyjaśnić ten krok bardziej szczegółowo)
--gold
nie pasuje; w pewnym momencie (czy to w tej początkowej pętli, czy później) zostanie umieszczona na liście extras
. (Sprawdzę kod po szczegóły).
Dla drugiej pętli przez ciągi znaków alternatywnie próbuje obsłużyć postionale i opcje.
Jest to próba dopasowania 5
z starttime
, że twoja klasa Akcja podnosi błąd typu, który propaguje do drukowania użycia i wyjścia. Ponieważ --gold
nie jest zdefiniowany, 5
nie jest używany jako argument opcjonalny.W ten sposób zostanie on przeanalizowany jako pierwszy łańcuch pozycyjny. (Niektóre rodzaje opcji pobierają 0 argumentów, więc nie zakłada niczego po tym, jak --...
jest argumentem opcji).
Myślę, że bez numeru 5
ostatni ciąg byłby zgodny. parse_known_args
powróciłby z --gold
w terminie extras
. parse_args
używa parse_known_args
, ale wywołuje błąd, gdy extras
nie jest pusta.
W pewnym sensie analizator składni wykrywa oba błędy, ale jest to jeden, który uruchamia komunikat o błędzie. Czeka na koniec, aby narzekać na nierozpoznane --gold
.
Jako ogólna filozofia, argparse
nie próbuje wykryć i przedstawić wszystkich błędów. Nie gromadzi listy błędów do przedstawienia w jednym ostatecznym kompleksowym komunikacie.
Sprawdzę kod, aby sprawdzić szczegóły. Nie sądzę, że można łatwo zmienić podstawowy wzorzec przetwarzania. Jeśli pomyślę o sposobie wymuszenia wcześniejszego błędu unrecognized option
, edytuję tę odpowiedź.
def _parse_optional(self, arg_string):
próbuje klasyfikowania argv
ciąg. Jeśli ciąg wygląda jak positional
, zwraca None
. Jeśli pasuje do opcji Option_string, zwraca krotkę "(action, option_string, None)" z pasującym działaniem. Wreszcie, jeśli nie pasuje, to zwraca:
# it was meant to be an optional but there is no such option
# in this parser (though it might be a valid option in a subparser)
return None, arg_string, None
myślę, że to, co dzieje się z Twoim --gold
. Zwróć uwagę na przyczynę, dla której może to być nadal poprawna opcja.
Ta funkcja jest wywoływana przez
def _parse_known_args(self, arg_strings, namespace):
...
for i, arg_string in enumerate(arg_strings_iter):
....
option_tuple = self._parse_optional(arg_string)
if option_tuple is None:
pattern = 'A'
else:
option_string_indices[i] = option_tuple
pattern = 'O'
arg_string_pattern_parts.append(pattern)
...
# at the end
# return the updated namespace and the extra arguments
return namespace, extras
zbierania że 'AOO'
wzorca, a także wykaz tych krotek.
Podczas drugiej pętli na przemian przechwytuje pozycje i opcje. Funkcja, która zużywa opcjonalny jest:
def consume_optional(start_index):
option_tuple = option_string_indices[start_index]
action, option_string, explicit_arg = option_tuple
if action is None:
extras.append(arg_strings[start_index])
...otherwise...
take_action(action, args, option_string)
Jak pisałem wcześniej, twój --gold
zostaje wpisany na listę extras
, natomiast 5
pozostaje na liście argumentów, które mogą być analizowane jako positionals.
namespace
i extras
są przekazywane za pośrednictwem użytkownika parse_known_args
do użytkownika lub do parse_args
.
Możliwe, że można podklasę ArgumentParser
i zdefiniować zmodyfikowaną metodę _parse_optional
. Może to spowodować błąd zamiast zwracać tę krotkę.
import argparse
import datetime
class MyParser(argparse.ArgumentParser):
def _parse_optional(self, arg_string):
arg_tuple = super(MyParser, self)._parse_optional(arg_string)
if arg_tuple is None:
return arg_tuple # positional
else:
if arg_tuple[0] is not None:
return arg_tuple # valid optional
else:
msg = 'error: no such option: %s'%arg_string
self.error(msg)
def convertIsoTime(timestamp):
"""read ISO-8601 time-stamp using the AMS conventional format YYYY-MM-DDThh:mm:ssUTC"""
try:
return datetime.datetime.strptime(timestamp,"%Y-%m-%dT%H:%M:%SUTC")
except:
raise argparse.ArgumentTypeError("'{}' is not a valid ISO-8601 time-stamp".format(timestamp))
# parser = argparse.ArgumentParser()
parser = MyParser()
parser.add_argument('startTime', type=convertIsoTime)
parser.add_argument('--good', type=int,
help='foo')
args = parser.parse_args(['--good','5','2015-01-01T00:00:00UTC'])
print(args)
args = parser.parse_args(['--gold','5','2015-01-01T00:00:00UTC'])
produkuje
1505:~/mypy$ python3 stack31317166.py
Namespace(good=5, startTime=datetime.datetime(2015, 1, 1, 0, 0))
usage: stack31317166.py [-h] [--good GOOD] startTime
stack31317166.py: error: error: no such option: --gold
Utworzenie podklasy dostarczyć niestandardowe działanie jest dobre argparse
(i Python) praktyka.
Jeśli chcesz dokładniej przeanalizować ten przypadek przez programistów Python, rozważ napisanie bug/issue
(w PEP jest dla bardziej rozwiniętych pomysłów formalnych). Istnieje jednak sporo zaległości w postaci błędów/poprawek argparse
i wiele uwagi na temat kompatybilności wstecznej.
http://bugs.python.org/issue?%40columns=id%2Cactivity%2Ctitle%2Ccreator%2Cassignee%2Cstatus%2Ctype&%40sort=-activity&%40filter=status&%40action=searchid&ignore=file%3Acontent&%40search_text=_parse_optional&submit=search&status=-1%2C1%2C2%2C3
znajduje się lista błędów/problemów odwołujących _parse_optional
. Możliwe zmiany obejmują obsługę niejednoznacznych opcji. (Zeskanuję je, aby sprawdzić, czy coś zapomniałem, niektóre poprawki są moje.) Ale korzystając z super
, na moją sugerowaną zmianę nie mają wpływu zmiany w funkcji. Wpływają na nią tylko zmiany w sposobie wywoływania funkcji i jej powrocie, co jest znacznie mniej prawdopodobne. Zgłaszając swój problem, przynajmniej informujesz programistów, że od tego interfejsu zależy ktoś.
Dzięki za szczegółowe i użyteczne wyjaśnienie. Moim jedynym zmartwieniem byłoby to, że może się zepsuć, jeśli argparse zostanie zaktualizowany. Dodam prośbę do zaległości argparse. –
Dodałem notatkę o istniejących problemach. – hpaulj