2015-07-09 9 views
5

Podczas korzystania z modułu argparse w Pythonie, szukam sposobu na uwięzienie nieprawidłowych opcji i zgłoszenie ich lepiej. Dokumentacja w https://docs.python.org/3/library/argparse.html#invalid-arguments stanowi przykład:najpierw zgłasza nieprawidłowe opcje (lub używa wyrażeń regularnych) z python argparse module

parser = argparse.ArgumentParser(prog='PROG' 
parser.add_argument('--foo', type=int) 
parser.add_argument('bar', nargs='?') 

# invalid option 
parser.parse_args(['--bar']) 
usage: PROG [-h] [--foo FOO] [bar] 
PROG: error: no such option: --bar 

Jednak jest to dość łatwe do podróży to jako złe opcje nie są zgłaszane jako pierwszy. Na przykład:

import argparse 
import datetime 

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.add_argument('startTime', type=convertIsoTime) 
parser.add_argument('--good', type=int, 
        help='foo') 

args = parser.parse_args(['--gold','5','2015-01-01T00:00:00UTC']) 

zgłosi:

error: argument startTime: '5' is not a valid ISO-8601 time-stamp 

Kiedy wolałbym go zgłosić bardziej przydatne:

error: no such option: --gold 

Czy to możliwe, aby osiągnąć ten cel? Wydaje mi się, że jest to dość podstawowy przypadek użycia. Podczas bezpośredniego zapisywania analizatorów argumentów zazwyczaj używam wzorca tak, że wszystko zaczynające się od prefiksu opcji -, który nie jest znaną opcją, jest natychmiast odrzucane. Na przykład w bash

# Process command-line arguments 
while [ $# -gt 0 ]; do 
    case "$1" in 
    --debug) 
     DEBUGOPTION="--debug" 
     shift 
     break;; 
    --) 
     shift 
     break;; 
    --*) 
     handleUsageError "$1" 
     shift;; 
    *) 
     break;; 
    esac 
done 

wierzę argparse używa wyrażeń regularnych wewnętrznie, ale nie sądzę, są one dostępne za pośrednictwem add_argument()

Czy istnieje jakiś sposób, aby zrobić równowartość łatwo argparse?

Odpowiedz

1

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 -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ś.

+0

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. –

+0

Dodałem notatkę o istniejących problemach. – hpaulj

Powiązane problemy