Come posso passare un elenco come argomento da riga di comando con argparse?


443

Sto cercando di passare un elenco come argomento a un programma da riga di comando. Esiste argparseun'opzione per passare un elenco come opzione?

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

Lo script si chiama come sotto

python test.py -l "265340 268738 270774 270817"

Risposte:


882

TL; DR

Utilizzare l' nargsopzione o l' 'append'impostazione actiondell'opzione (a seconda di come si desidera che l'interfaccia utente si comporti).

nargs

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

nargs='+'accetta 1 o più argomenti, nargs='*'accetta zero o più.

aggiungere

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

Con appendte fornisci l'opzione più volte per costruire l'elenco.

Non usare type=list!!! - Probabilmente non esiste situazione in cui si desidera utilizzare type=listcon argparse. Mai.


Diamo un'occhiata più in dettaglio ad alcuni dei diversi modi in cui si potrebbe provare a fare questo, e il risultato finale.

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)

Ecco l'output che puoi aspettarti:

$ 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']

Asporto :

  • Usa nargsoaction='append'
    • nargspuò essere più semplice dal punto di vista dell'utente, ma può non essere intuitivo se ci sono argomenti posizionali perché argparsenon può dire quale dovrebbe essere un argomento posizionale e ciò che appartiene a nargs; se hai argomenti posizionali, action='append'potresti finire per essere una scelta migliore.
    • Quanto sopra è solo vero se nargsè dato '*', '+'o '?'. Se si fornisce un numero intero (come 4), non ci saranno problemi a mescolare opzioni nargse argomenti posizionali perché argparsesaprà esattamente quanti valori aspettarsi per l'opzione.
  • Non usare le virgolette sulla riga di comando 1
  • Non utilizzare type=list, poiché restituirà un elenco di elenchi
    • Questo accade perché sotto il cofano si argparseusa il valore di typeforzare ogni singolo argomento dato che hai scelto type, non l'aggregato di tutti gli argomenti.
    • Puoi usare type=int(o qualsiasi altra cosa) per ottenere un elenco di ints (o qualunque altra cosa)

1 : Non intendo in generale .. Voglio dire che usare le virgolette per passare un elencoargparse non è quello che vuoi.


3
Che dire di un elenco di stringhe? Ciò trasforma più argomenti stringa ("wassup", "qualcosa" e "altro") in un elenco di elenchi che assomiglia a questo: [['w', 'a', 's', 's', 'u' , 'p'], ['s', 'o', 'm', 'e', ​​'t', 'h', 'i', 'n', 'g'], ['e', ' l ',' s ',' e ']]
rd108

3
@ rd108 Vedo, scommetto che stai usando l' type=listopzione. Non usarlo. Ciò trasforma una stringa in un elenco, e quindi gli elenchi di elenchi.
SethMMorton,

1
@Dror Si presume che tutti gli input siano stringhe a meno che non si imposti il typeparametro su qualche altro oggetto. Per impostazione predefinita, questo metodo restituisce un elenco di stringhe.
SethMMorton,

1
--potrebbe dividere le opzioni rispetto agli argomenti posizionali. prog --opt1 par1 ... -- posp1 posp2 ...
0andriy,

1
può non essere intuitivo se ci sono argomenti posizionali perché argparse non può dire quale dovrebbe essere un argomento posizionale e cosa appartiene ai nargs . --aiuta a capire come mostrato nell'esempio nel mio commento precedente. Forniture per utenti IOW --seguite da tutti gli argomenti posizionali.
0andriy,

83

Preferisco passare una stringa delimitata che analizzerò più avanti nella sceneggiatura. Le ragioni sono: l'elenco può essere di qualsiasi tipo into str, e a volte usando nargsmi imbatto in problemi se ci sono più argomenti opzionali e argomenti posizionali.

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(',')]

Poi,

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

o,

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

funzionerà bene. Anche il delimitatore può essere uno spazio, che tuttavia imporrebbe virgolette attorno al valore dell'argomento come nell'esempio nella domanda.


57
È possibile impostare l' typeargomento su lambda s: [int(time) for item in s.split(',')]invece di post-elaborazione args.list.
Chepner,

13
@ chepner, sì hai perfettamente ragione e sarebbe più pitonico - solo un piccolo errore di battitura: int(time)dovrebbe essere int(item). Il mio esempio era una versione semplificata di ciò che faccio in genere, in cui controllo molte altre cose anziché una semplice elaborazione. Ma per rispondere semplicemente alla domanda, anch'io trovo la tua strada più elegante ..
dojuba,

1
questa risposta sembra essere la più pitonica
Quetzalcoatl

1
Il commento di @chepner è un serio ninja skillz +1
Briford Wylie,

1
lambda items: list(csv.reader([items]))[0]con la libreria CSV standard è una versione modificata del commento di @chepner per chiunque sia preoccupato per l'input CSV arbitrario (rif: risposta da @adamk ).
Kevin,

19

Inoltre nargs, potresti voler utilizzare choicesse conosci in anticipo l'elenco:

>>> 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')

10

Utilizzo del parametro nargs nel metodo add_argument di argparse

Uso nargs = ' ' come parametro add_argument. Ho usato specificamente nargs = ' ' per selezionare le impostazioni predefinite se non sto passando argomenti espliciti

Compreso uno snippet di codice come esempio:

Esempio: temp_args1.py

Nota: il seguente codice di esempio è scritto in python3. Modificando il formato dell'istruzione print, può essere eseguito in 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))

Nota: sto raccogliendo più argomenti stringa che vengono memorizzati nell'elenco - opts.alist Se si desidera un elenco di numeri interi, modificare il parametro type su parser.add_argument in int

Risultato dell'esecuzione:

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']

1
@Py_minion C'è un modo per usare una lista come argomento e avere anche l'output come lista? temp_args1.py -i [item5 ,item6, item7]e far uscire anche l'output come elenco (anziché come elenco nidificato)
Moondra,

@Moondra Sì. felice che tu l'abbia chiesto. `` `parser.add_argument ('- o', '--options', action = 'store', dest = 'opt_list', type = str, nargs = '*', default = sample_list, help =" Stringa di database separati da spazi bianchi. Esempi: \ -o opzione1 opzione2, -o opzione3 ")` `` Qui 'elenco_campioni' è di tipo elenco con opzioni predefinite. Esempio: esempio_elenco = [opzione4, opzione5]
Py_minion

1
@Py_minion Grazie. Lo testerò più tardi oggi.
Moondra,

Ho usato questo, questo è molto utile per passare la creazione di liste dagli argomenti.
siby,

5

Se si intende fare in modo che un singolo switch accetti più parametri, allora si usa nargs='+'. Se il tuo esempio '-l' sta effettivamente prendendo numeri interi:

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(' '))

produce

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

Se si specifica più volte lo stesso argomento, l'azione predefinita ( 'store') sostituisce i dati esistenti.

L'alternativa è usare l' appendazione:

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(' '))

Quale produce

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

Oppure puoi scrivere un gestore / azione personalizzato per analizzare i valori separati da virgola in modo da poterlo fare

-l 123,234,345 -l 456

5

In add_argument(), typeè solo un oggetto richiamabile che riceve la stringa e restituisce il valore dell'opzione.

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")

Ciò consentirà di:

$ ./tool --list "[1,2,3,4]"

Si noti che se fosse necessario passare stringhe, questo metodo richiederebbe che le citino in modo appropriato sulla riga di comando. Un utente potrebbe trovarlo inaspettato. Se solo analizzando numeri interi questo va bene.
SethMMorton,

1

Se disponi di un elenco nidificato in cui gli elenchi interni hanno tipi e lunghezze diverse e desideri preservare il tipo, ad es.

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

quindi puoi utilizzare la soluzione proposta da @ sam-mason a questa domanda , mostrata di seguito:

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]]'])

che dà:

Namespace(l=[[1, 2], ['foo', 'bar'], [3.14, 'baz', 20]])

0

Voglio gestire il passaggio di più liste, valori interi e stringhe.

Link utile => Come passare una variabile Bash a Python?

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:])

L'ordine non è importante. Se si vuole passare un elenco basta fare come in mezzo "["e "]e separare utilizzando una virgola.

Poi,

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

Output => ['my_string', '3', ['1', '2'], ['3', '4', '5']], la my_argsvariabile contiene gli argomenti in ordine.


0

Penso che la soluzione più elegante sia passare una "funzione lambda" al "tipo", come menzionato da Chepner. Inoltre, se non sai in anticipo quale sarà il delimitatore del tuo elenco, puoi anche passare più delimitatori a 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']

Intendevi -lnella chiamata di esempio? Da dove -nviene?
Anthony,

Inoltre, la soluzione non funziona per me in Python 3.8.2. Ecco il codice: parser.add_argument('-l', '--list', type = lambda s: re.split('[ ,;]', s)). Qui è l'ingresso: script.py -l abc xyz, abc\nxyz. Infine, ecco il risultato:script.py: error: unrecognized arguments: xyz, abcnxyz
Anthony,

Cambia il mio esempio in modo che funzioni :)
Nebulastic
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.