2014-12-11 9 views
11

Chciałbym wiedzieć, jak używać modułu argparse Pythona do odczytu argumentów zarówno z linii poleceń i ewentualnie z plików tekstowych. Wiem o argparse'owym fromfile_prefix_chars, ale nie jest to dokładnie to, czego chcę. Chcę tego zachowania, ale nie chcę składni. Chcę interfejs, który wygląda tak:jak uzyskać argparse odczytać argumenty z pliku z opcją zamiast prefiksu

$ python myprogram.py --foo 1 -A somefile.txt --bar 2 

Kiedy argparse widzi -a, powinien przestać czytać z sys.argv lub cokolwiek daję, i wywołać funkcję piszę, że będzie czytać somefile.text i powrotu lista argumentów. Po wyczerpaniu pliku należy wznowić przetwarzanie sys.argv lub cokolwiek innego. Ważne jest, aby przetwarzanie argumentów w pliku odbywało się w kolejności (tj .: -foo powinno być przetwarzane, następnie argumenty w pliku, a następnie -bar, aby argumenty w pliku mogły przesłonić --foo, i - pasek może przesłonić zawartość pliku).

Czy coś takiego jest możliwe? Czy mogę napisać niestandardową funkcję, która wypycha nowe argumenty na stos argparse'a, czy coś podobnego?

+0

W twojej wersji, co wywołałoby specjalne traktowanie argumentu (zamiast znaku prefiksu)? – martineau

+0

@martineau: opcja "-A" mówi "następnym argumentem jest plik do odczytania". Muszę być w stanie napisać własną funkcję, aby odczytać ten plik i zwrócić argumenty (nie jest to format jednoargumentowy w linii). –

+0

Patrząc na [źródło _parse_known_args] (https: //hg.python. org/cpython/file/3.4/Lib/argparse.py # l1769), wygląda na to, że argparse lubi znać wszystkie argumenty z góry. Jeśli masz ustawioną opcję @fromfile_prefix_chars, to sprawdza argumenty i buduje zupełnie nową listę do przeanalizowania (_read_args_from_files). Myślę, że najlepszą opcją jest sparsowanie sys.argv z wyprzedzeniem, aby uzyskać listę argumentów. – mgilson

Odpowiedz

19

Problem ten można rozwiązać przy użyciu niestandardowego argparse.Action który otwiera plik, analizuje zawartość pliku, a następnie dodaje argumenty potem.

Przykładowo będzie to bardzo proste działanie:

class LoadFromFile (argparse.Action): 
    def __call__ (self, parser, namespace, values, option_string = None): 
     with values as f: 
      parser.parse_args(f.read().split(), namespace) 

których można wykorzystanie tak:

parser = argparse.ArgumentParser() 
# other arguments 
parser.add_argument('--file', type=open, action=LoadFromFile) 
args = parser.parse_args() 

Powstały nazw w args wtedy również zawierać dowolną konfigurację, która była również załadowany z pliku.

Jeśli potrzebujesz bardziej wyrafinowanego analizowania, możesz najpierw oddzielnie przeanalizować konfigurację pliku, a następnie selektywnie wybrać, które wartości mają zostać przejęte. Na przykład, może to sens, aby nie pozwolić na określenie inny plik w pliku konfiguracyjnym:

def __call__ (self, parser, namespace, values, option_string=None): 
    with values as f: 
     contents = f.read() 

    data = parser.parse_args(contents.split(), namespace=namespace) 
    for k, v in vars(data).items(): 
     if v and k != option_string.lstrip('-'): 
      setattr(namespace, k, v) 

Oczywiście, można również dokonać odczytu pliku nieco bardziej skomplikowane, na przykład odczytać z JSON pierwszy.

+0

Ah-ha! Kluczem jest to, że parser jest przekazywany do akcji niestandardowej i możesz użyć tego parsera do przetworzenia zawartości pliku. Dziękuję Ci. –

+0

Ale jeśli analizator składni miałby inną nazwę w środowisku globalnym, nadal byłby dostępny tutaj. Tak więc można użyć dowolnego parsera. Nie ma nic szczególnego w parametrze "parser" przekazanym jako parametr. – hpaulj

+1

Niestety, to się nie powiedzie, jeśli którykolwiek z parametrów zostanie ustawiony na Wymagany. –

1

Po wywołaniu Action otrzymuje wśród swoich argumentów parser i namespace.

dzięki czemu można umieścić plik przez byłego zaktualizować ten ostatni:

class ArgfileAction(argparse.Action): 
    def __call__(self, parser, namespace, values, option_string=None): 
     extra_args = <parse_the_file>(values) 
     #`namespace' is updated in-place when specified 
     parser.parse_args(extra_args,namespace) 
+3

Wierzę, że OP już wie o tej funkcji, jak wskazano w oświadczeniu "Wiem o argparse fromfile_prefix_chars, ale to nie jest dokładnie to, co chcę" – mgilson

2

Państwo zauważyli, że

muszę być w stanie napisać własną funkcję, aby odczytać ten plik i powrócić argumenty (nie jest w formacie jednego argumentu-per-line) -

W istniejącym module obsługi plików prefiksu istnieje postanowienie umożliwiające zmianę sposobu odczytywania pliku. Plik jest odczytywany przez „prywatny” metody, parser._read_args_from_files, ale wywołuje prostą metodę publiczną, która przekształca linię do strun, domyślna akcja jednym argumentem-per-line:

def convert_arg_line_to_args(self, arg_line): 
    return [arg_line] 

został napisany w ten sposób tak można go łatwo dostosować. https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

Użyteczny obejścia tego sposobu jest, że traktuje każde słowo przestrzennie oddzielone jako argumentu

W test jednostkowy pliku test_argparse.py jest sprawdzian tego wariantu.


Ale jeśli nadal chcesz wywołać ten odczyt z opcją argumentu, zamiast znaku prefiksu, to niestandardowe podejście akcji jest dobre.

Można napisać własną funkcję, która przetwarza argv, zanim zostanie przekazana do parser. Może być modelowany na parser._read_args_from_files.

Więc można napisać funkcję, jak:

def read_my_file(argv): 
    # if there is a '-A' string in argv, replace it, and the following filename 
    # with the contents of the file (as strings) 
    # you can adapt code from _read_args_from_files 
    new_argv = [] 
    for a in argv: 
     .... 
     # details left to user 
    return new_argv 

Następnie powołać swój parser z:

parser.parse_args(read_my_file(sys.argv[1:])) 

i tak, to może być owinięty w ArgumentParser podklasy.

Powiązane problemy