Jak mogę przekazać listę jako argument wiersza poleceń z argparse?

442

Próbuję przekazać listę jako argument do programu wiersza polecenia. Czy istnieje argparseopcja przekazania listy jako opcji?

parser.add_argument('-l', '--list',
                      type=list, action='store',
                      dest='list',
                      help='<Required> Set flag',
                      required=True)

Skrypt nazywa się jak poniżej

python test.py -l "265340 268738 270774 270817"
użytkownik2125827
źródło

Odpowiedzi:

880

TL; DR

Skorzystaj z nargsopcji lub jej 'append'ustawienia action(w zależności od tego, jak ma się zachowywać interfejs użytkownika).

nargs

parser.add_argument('-l','--list', nargs='+', help='<Required> Set flag', required=True)
# Use like:
# python arg.py -l 1234 2345 3456 4567

nargs='+'przyjmuje 1 lub więcej argumentów, nargs='*'przyjmuje zero lub więcej.

dodać

parser.add_argument('-l','--list', action='append', help='<Required> Set flag', required=True)
# Use like:
# python arg.py -l 1234 -l 2345 -l 3456 -l 4567

Dzięki appendwielokrotnej opcji możesz zbudować listę.

Nie używaj type=list!!! - Prawdopodobnie nie ma sytuacji, w której chciałbyś używać type=listz argparse. Zawsze.


Przyjrzyjmy się bardziej szczegółowo niektórym różnym sposobom, w jakie można to zrobić, i efektowi końcowemu.

import argparse

parser = argparse.ArgumentParser()

# By default it will fail with multiple arguments.
parser.add_argument('--default')

# Telling the type to be a list will also fail for multiple arguments,
# but give incorrect results for a single argument.
parser.add_argument('--list-type', type=list)

# This will allow you to provide multiple arguments, but you will get
# a list of lists which is not desired.
parser.add_argument('--list-type-nargs', type=list, nargs='+')

# This is the correct way to handle accepting multiple arguments.
# '+' == 1 or more.
# '*' == 0 or more.
# '?' == 0 or 1.
# An int is an explicit number of arguments to accept.
parser.add_argument('--nargs', nargs='+')

# To make the input integers
parser.add_argument('--nargs-int-type', nargs='+', type=int)

# An alternate way to accept multiple inputs, but you must
# provide the flag once per input. Of course, you can use
# type=int here if you want.
parser.add_argument('--append-action', action='append')

# To show the results of the given option to screen.
for _, value in parser.parse_args()._get_kwargs():
    if value is not None:
        print(value)

Oto wynik, którego możesz się spodziewać:

$ python arg.py --default 1234 2345 3456 4567
...
arg.py: error: unrecognized arguments: 2345 3456 4567

$ python arg.py --list-type 1234 2345 3456 4567
...
arg.py: error: unrecognized arguments: 2345 3456 4567

$ # Quotes won't help here... 
$ python arg.py --list-type "1234 2345 3456 4567"
['1', '2', '3', '4', ' ', '2', '3', '4', '5', ' ', '3', '4', '5', '6', ' ', '4', '5', '6', '7']

$ python arg.py --list-type-nargs 1234 2345 3456 4567
[['1', '2', '3', '4'], ['2', '3', '4', '5'], ['3', '4', '5', '6'], ['4', '5', '6', '7']]

$ python arg.py --nargs 1234 2345 3456 4567
['1234', '2345', '3456', '4567']

$ python arg.py --nargs-int-type 1234 2345 3456 4567
[1234, 2345, 3456, 4567]

$ # Negative numbers are handled perfectly fine out of the box.
$ python arg.py --nargs-int-type -1234 2345 -3456 4567
[-1234, 2345, -3456, 4567]

$ python arg.py --append-action 1234 --append-action 2345 --append-action 3456 --append-action 4567
['1234', '2345', '3456', '4567']

Na wynos :

  • Użyj nargslubaction='append'
    • nargsmoże być prostsze z punktu widzenia użytkownika, ale może być nieintuicyjne, jeśli istnieją argumenty pozycyjne, ponieważ argparsenie można powiedzieć, co powinno być argumentem pozycyjnym, a co należy do nargs; jeśli masz argumenty pozycyjne, action='append'może być lepszym wyborem.
    • Powyższe jest prawdziwe tylko wtedy, gdy nargsjest podany '*', '+'lub '?'. Jeśli podasz liczbę całkowitą (np. 4), Nie będzie problemu z mieszaniem opcji z nargsargumentami pozycyjnymi, ponieważ argparsebędziesz wiedział dokładnie, ile wartości można oczekiwać dla opcji.
  • Nie używaj cudzysłowów w wierszu poleceń 1
  • Nie używaj type=list, ponieważ spowoduje to zwrócenie listy list
    • Dzieje się tak, ponieważ pod maską argparseużywa się wartości, typeaby wymusić każdy wybrany argumenttype , a nie sumę wszystkich argumentów.
    • Możesz użyć type=int(lub cokolwiek), aby uzyskać listę ints (lub cokolwiek)

1 : Ogólnie nie mam na myśli .. Mam na myśli, że przekazywanie listyargparse cudzysłowami nie jest tym, czego chcesz.

SethMMorton
źródło
3
Co z listą ciągów? To zmienia wiele argumentów łańcuchowych („wassup”, „coś” i „else”) w listę list, która wygląda następująco: [['w', 'a', 's', 's', 'u' , „p”], [„s”, „o”, „m”, „e”, „t”, „h”, „i”, „n”, „g”], [„e”, „ l ',' s ',' e ']]
rd108
3
@ rd108 Rozumiem, założę się, że korzystasz z type=listopcji. Nie używaj tego. To zamienia ciąg znaków w listę, a tym samym listy list.
SethMMorton
1
@Dror Zakłada się, że wszystkie dane wejściowe są ciągami znaków, chyba że typeparametr zostanie ustawiony na inny obiekt. Domyślnie ta metoda zwraca listę ciągów znaków.
SethMMorton
1
--może podzielić opcje na argumenty pozycyjne. prog --opt1 par1 ... -- posp1 posp2 ...
0andriy
1
może być nieintuicyjna, jeśli istnieją argumenty pozycyjne, ponieważ argparse nie może powiedzieć, co powinno być argumentem pozycyjnym, a co należy do nargs . --pomaga to rozgryźć, jak pokazano na przykładzie w poprzednim komentarzu. Dostawy IOW użytkownika --wraz z wszystkimi argumentami pozycyjnymi.
0andriy
83

Wolę przekazywać ciąg ograniczony, który analizuję później w skrypcie. Przyczyny tego są; lista może być dowolnego typu intlub str, a czasem przy użyciu nargsI napotkał problemy, jeśli istnieje wiele argumentów opcjonalnych i argumentów pozycyjnych.

parser = ArgumentParser()
parser.add_argument('-l', '--list', help='delimited list input', type=str)
args = parser.parse_args()
my_list = [int(item) for item in args.list.split(',')]

Następnie,

python test.py -l "265340,268738,270774,270817" [other arguments]

lub,

python test.py -l 265340,268738,270774,270817 [other arguments]

będzie dobrze działać. Separator może być również spacją, która wymusiłaby cytaty wokół wartości argumentu, jak w przykładzie w pytaniu.

dojuba
źródło
57
Możesz ustawić typeargument na lambda s: [int(time) for item in s.split(',')]zamiast przetwarzania końcowego args.list.
chepner
13
@ chepner, tak, masz absolutną rację i byłoby bardziej pythonowe - wystarczy mała literówka: int(time)powinno być int(item). Mój przykład był uproszczoną wersją tego, co zwykle robię, gdzie sprawdzam wiele innych rzeczy, a nie proste przetwarzanie. Ale po prostu odpowiadając na pytanie, ja też uważam twoją drogę za bardziej elegancką ..
dojuba
1
ta odpowiedź wydaje się być najbardziej pytoniczna
Quetzalcoatl
1
Komentarz @chepner to poważna umiejętność ninja +1
Briford Wylie
1
lambda items: list(csv.reader([items]))[0]ze standardową biblioteką csv jest zmodyfikowaną wersją komentarza od @chepner dla każdego, kto martwi się o arbitralne dane wejściowe CSV (zob .: odpowiedź od @adamk ).
Kevin
19

Ponadto nargsmożesz skorzystać, choicesjeśli znasz listę z góry:

>>> parser = argparse.ArgumentParser(prog='game.py')
>>> parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
>>> parser.parse_args(['rock'])
Namespace(move='rock')
>>> parser.parse_args(['fire'])
usage: game.py [-h] {rock,paper,scissors}
game.py: error: argument move: invalid choice: 'fire' (choose from 'rock',
'paper', 'scissors')
Martin Thoma
źródło
10

Użycie parametru nargs w metodzie add_argument argparse

Używam nargs = ' ' jako parametru add_argument. W szczególności użyłem nargs = ' ' do opcji wyboru wartości domyślnych, jeśli nie przekazuję żadnych wyraźnych argumentów

W tym fragment kodu jako przykład:

Przykład: temp_args1.py

Uwaga: poniższy przykładowy kod został napisany w python3. Zmieniając format instrukcji print, można uruchomić w python2

#!/usr/local/bin/python3.6

from argparse import ArgumentParser

description = 'testing for passing multiple arguments and to get list of args'
parser = ArgumentParser(description=description)
parser.add_argument('-i', '--item', action='store', dest='alist',
                    type=str, nargs='*', default=['item1', 'item2', 'item3'],
                    help="Examples: -i item1 item2, -i item3")
opts = parser.parse_args()

print("List of items: {}".format(opts.alist))

Uwaga: zbieram wiele argumentów łańcuchowych, które są przechowywane na liście - opts.alist Jeśli chcesz listę liczb całkowitych, zmień parametr type w parser.add_argument na int

Wynik wykonania:

python3.6 temp_agrs1.py -i item5 item6 item7
List of items: ['item5', 'item6', 'item7']

python3.6 temp_agrs1.py -i item10
List of items: ['item10']

python3.6 temp_agrs1.py
List of items: ['item1', 'item2', 'item3']
Py_minion
źródło
1
@Py_minion Czy istnieje sposób na użycie listy jako argumentu, a także wyjście jako listę? temp_args1.py -i [item5 ,item6, item7]i
niech
@Moondra Tak. cieszę się, że zapytałeś. `` `parser.add_argument ('- o', '--options', action = 'store', dest = 'opt_list', type = str, nargs = '*', default = sample_list, help =" Ciąg baz danych oddzielone spacją. Przykłady: \ -o opcja1 opcja2, -o opcja3 ")` `Tutaj 'lista_próbek' jest listą typów z opcjami domyślnymi. Przykład: lista_próbek = [opcja4, opcja5]
Py_minion
1
@Py_minion Dziękujemy. Zamierzam przetestować to później dzisiaj.
Moondra
Użyłem tego, jest to bardzo przydatne do przekazywania tworzenia list z argumentów.
rodzeństwo
5

Jeśli zamierzasz sprawić, aby pojedynczy przełącznik przyjmował wiele parametrów, użyj nargs='+'. Jeśli twój przykład „-l” faktycznie przyjmuje liczby całkowite:

a = argparse.ArgumentParser()
a.add_argument(
    '-l', '--list',  # either of this switches
    nargs='+',       # one or more parameters to this switch
    type=int,        # /parameters/ are ints
    dest='list',     # store in 'list'.
    default=[],      # since we're not specifying required.
)

print a.parse_args("-l 123 234 345 456".split(' '))
print a.parse_args("-l 123 -l=234 -l345 --list 456".split(' '))

Produkuje

Namespace(list=[123, 234, 345, 456])
Namespace(list=[456])  # Attention!

Jeśli podasz ten sam argument wiele razy, domyślna akcja ( 'store') zastępuje istniejące dane.

Alternatywą jest użycie appendakcji:

a = argparse.ArgumentParser()
a.add_argument(
    '-l', '--list',  # either of this switches
    type=int,        # /parameters/ are ints
    dest='list',     # store in 'list'.
    default=[],      # since we're not specifying required.
    action='append', # add to the list instead of replacing it
)

print a.parse_args("-l 123 -l=234 -l345 --list 456".split(' '))

Który produkuje

Namespace(list=[123, 234, 345, 456])

Możesz też napisać niestandardowy moduł obsługi / akcji, aby przeanalizować wartości oddzielone przecinkami, abyś mógł to zrobić

-l 123,234,345 -l 456
kfsone
źródło
5

w add_argument(),type jest po prostu wywoływalnym obiekt, który odbiera ciąg i zwraca wartość opcji.

import ast

def arg_as_list(s):                                                            
    v = ast.literal_eval(s)                                                    
    if type(v) is not list:                                                    
        raise argparse.ArgumentTypeError("Argument \"%s\" is not a list" % (s))
    return v                                                                   


def foo():
    parser.add_argument("--list", type=arg_as_list, default=[],
                        help="List of values")

Umożliwi to:

$ ./tool --list "[1,2,3,4]"
wonder.mice
źródło
Zauważ, że jeśli trzeba przekazać ciągi, ta metoda wymaga odpowiedniego zacytowania ich w wierszu poleceń. Użytkownik może uznać to za nieoczekiwane. Jeśli tylko parsowanie liczb całkowitych jest w porządku.
SethMMorton
1

Jeśli masz zagnieżdżoną listę, w której wewnętrzne listy mają różne typy i długości i chcesz zachować typ, np.

[[1, 2], ["foo", "bar"], [3.14, "baz", 20]]

możesz skorzystać z rozwiązania zaproponowanego przez @ sam-mason na to pytanie , pokazanego poniżej:

from argparse import ArgumentParser
import json

parser = ArgumentParser()
parser.add_argument('-l', type=json.loads)
parser.parse_args(['-l', '[[1,2],["foo","bar"],[3.14,"baz",20]]'])

co daje:

Namespace(l=[[1, 2], ['foo', 'bar'], [3.14, 'baz', 20]])
Meysam Sadeghi
źródło
0

Chcę obsłużyć przekazywanie wielu list, wartości całkowitych i ciągów.

Pomocny link => Jak przekazać zmienną Bash do Pythona?

def main(args):
    my_args = []
    for arg in args:
        if arg.startswith("[") and arg.endswith("]"):
            arg = arg.replace("[", "").replace("]", "")
            my_args.append(arg.split(","))
        else:
            my_args.append(arg)

    print(my_args)


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

Porządek nie jest ważny. Jeśli chcesz przekazać listę prostu zrobić jak między "["a "]i oddzielić je za pomocą przecinka.

Następnie,

python test.py my_string 3 "[1,2]" "[3,4,5]"

Dane wyjściowe => ['my_string', '3', ['1', '2'], ['3', '4', '5']], my_argszmienna zawiera argumenty w kolejności.

Alper
źródło
0

Myślę, że najbardziej eleganckim rozwiązaniem jest przekazanie funkcji lambda do „pisma”, jak wspomniał Chepner. Ponadto, jeśli wcześniej nie wiesz, jaki będzie separator listy, możesz również przekazać wiele separatorów do re.split:

# python3 test.py -l "abc xyz, 123"

import re
import argparse

parser = argparse.ArgumentParser(description='Process a list.')
parser.add_argument('-l', '--list',
                    type=lambda s: re.split(' |, ', s),
                    required=True,
                    help='comma or space delimited list of characters')

args = parser.parse_args()
print(args.list)


# Output: ['abc', 'xyz', '123']
Mgławica
źródło
Czy chodziło Ci -lo przykładowe połączenie? Skąd się wziął -n?
Anthony
Ponadto rozwiązanie nie działa dla mnie w Python 3.8.2. Oto kod: parser.add_argument('-l', '--list', type = lambda s: re.split('[ ,;]', s)). Tutaj jest wejście: script.py -l abc xyz, abc\nxyz. Wreszcie, oto wynik:script.py: error: unrecognized arguments: xyz, abcnxyz
Anthony
Zmień mój przykład, aby
zadziałał