Wielokrotne używanie tej samej opcji w argparse Pythona

85

Próbuję napisać skrypt, który akceptuje wiele źródeł wejściowych i robi coś z każdym z nich. Coś takiego

./my_script.py \
    -i input1_url input1_name input1_other_var \
    -i input2_url input2_name input2_other_var \
    -i input3_url input3_name
# notice inputX_other_var is optional

Ale nie mogę się do końca dowiedzieć, jak to zrobić za pomocą argparse. Wygląda na to, że jest skonfigurowany tak, że każdej flagi opcji można użyć tylko raz. Wiem, jak powiązać wiele argumentów z jedną opcją ( nargs='*'lub nargs='+'), ale to nadal nie pozwala mi -iwielokrotnie używać flagi. Jak mam się do tego zabrać?

Żeby było jasne, na koniec chciałbym otrzymać listę list łańcuchów. Więc

[["input1_url", "input1_name", "input1_other"],
 ["input2_url", "input2_name", "input2_other"],
 ["input3_url", "input3_name"]]
John Allard
źródło
Dlaczego więc nie powiązać wielu argumentów źródła wejściowego z tą jedną opcją?
TigerhawkT3
Ponieważ każde z wielu źródeł wejściowych również musi mieć wiele argumentów w postaci ciągów. Chciałbym użyć flagi -i dla każdego wejścia, a każde wejście zawierałoby wszystkie łańcuchy między kolejnymi flagami -i. Chcę, żeby działał jak ffmpeg, gdzie określasz dane wejściowe za pomocą -i
John Allard

Odpowiedzi:

97

Oto parser, który obsługuje powtórzone 2 argumenty opcjonalne - z nazwami zdefiniowanymi w metavar:

parser=argparse.ArgumentParser()
parser.add_argument('-i','--input',action='append',nargs=2,
    metavar=('url','name'),help='help:')

In [295]: parser.print_help()
usage: ipython2.7 [-h] [-i url name]

optional arguments:
  -h, --help            show this help message and exit
  -i url name, --input url name
                        help:

In [296]: parser.parse_args('-i one two -i three four'.split())
Out[296]: Namespace(input=[['one', 'two'], ['three', 'four']])

To nie załatwia 2 or 3 argumentsprawy (chociaż napisałem jakiś czas temu łatkę na błąd / problem Pythona, który obsługiwałby taki zakres).

Co powiesz na oddzielną definicję argumentu z nargs=3i metavar=('url','name','other')?

Krotki metavarmożna również używać z nargs='+'i nargs='*'; 2 struny są używane jako [-u A [B ...]]lub [-u [A [B ...]]].

hpaulj
źródło
1
O, nieźle! Uwielbiam, jak funkcja pomocy pokazuje, co reprezentują poszczególne komponenty opcji wieloczęściowej. Będę tego używać!
John Allard
49

To jest proste; po prostu dodaj oba action='append'i nargs='*'(lub '+').

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', action='append', nargs='+')
args = parser.parse_args()

A kiedy go uruchomisz, otrzymasz

In [32]: run test.py -i input1_url input1_name input1_other_var -i input2_url i
...: nput2_name input2_other_var -i input3_url input3_name

In [33]: args.i
Out[33]:
[['input1_url', 'input1_name', 'input1_other_var'],
 ['input2_url', 'input2_name', 'input2_other_var'],
 ['input3_url', 'input3_name']]
Amir
źródło
2
Dzięki, dokładnie to, czego potrzebowałem! : D Nota boczna: możliwą wartością domyślną musi być typ list / array, w przeciwnym razie Argparse zawiedzie
Tarwin
22

-ipowinien być skonfigurowany tak, aby akceptował 3 argumenty i używał appendakcji.

>>> p = argparse.ArgumentParser()
>>> p.add_argument("-i", nargs=3, action='append')
_AppendAction(...)
>>> p.parse_args("-i a b c -i d e f -i g h i".split())
Namespace(i=[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']])

Aby obsłużyć opcjonalną wartość, możesz spróbować użyć prostego typu niestandardowego. W tym przypadku argument do -ijest pojedynczym łańcuchem rozdzielanym przecinkami, z liczbą podziałów ograniczoną do 2. Konieczne byłoby przetworzenie wartości końcowych, aby upewnić się, że określono co najmniej dwie wartości.

>>> p.add_argument("-i", type=lambda x: x.split(",", 2), action='append')
>>> print p.parse_args("-i a,b,c -i d,e -i g,h,i,j".split())
Namespace(i=[['a', 'b', 'c'], ['d', 'e'], ['g', 'h', 'i,j']])

Aby uzyskać większą kontrolę, zdefiniuj akcję niestandardową. Ten rozszerza wbudowane _AppendAction(używane przez action='append'), ale po prostu sprawdza zakres liczby argumentów podanych -i.

class TwoOrThree(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        if not (2 <= len(values) <= 3):
            raise argparse.ArgumentError(self, "%s takes 2 or 3 values, %d given" % (option_string, len(values)))
        super(TwoOrThree, self).__call__(parser, namespace, values, option_string)

p.add_argument("-i", nargs='+', action=TwoOrThree)
Chepner
źródło
1
Znakomity! Dziękuję za pomoc.
John Allard
2
Nie dość tego, co chcesz; Brakowało mi, że inputX_other_varjest to opcjonalne.
chepner
Właśnie wróciłem, aby skomentować jako taki, twój sposób wymaga wszystkich vars. Ale to dobry kierunek!
John Allard
1
OK, zaktualizowano kilkoma opcjami obsługi opcjonalnego trzeciego argumentu.
chepner