Como posso passar uma lista como argumento da linha de comando com argparse?

441

Estou tentando passar uma lista como argumento para um programa de linha de comando. Existe uma argparseopção para passar uma lista como opção?

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

Script é chamado como abaixo

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

Respostas:

879

TL; DR

Use a nargsopção ou a 'append'configuração da actionopção (dependendo de como você deseja que a interface do usuário se comporte).

nargs

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

nargs='+'leva 1 ou mais argumentos, nargs='*'leva zero ou mais.

acrescentar

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

Com appendvocê, forneça a opção várias vezes para criar a lista.

Não use type=list!!! - Provavelmente, não há situação em que você gostaria de usar type=listcom argparse. Sempre.


Vamos dar uma olhada em mais detalhes em algumas das maneiras diferentes de tentar fazer isso e no resultado final.

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)

Aqui está a saída que você pode esperar:

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

Takeaways :

  • Use nargsouaction='append'
    • nargspode ser mais direto da perspectiva do usuário, mas pode não ser intuitivo se houver argumentos posicionais, porque argparsenão é possível dizer o que deve ser um argumento posicional e o que pertence ao nargs; se você tiver argumentos posicionais, action='append'pode acabar sendo uma escolha melhor.
    • O acima só é verdadeiro se nargsé dada '*', '+'ou '?'. Se você fornecer um número inteiro (como 4), não haverá problema em misturar opções com nargsargumentos posicionais, pois argparsesaberá exatamente quantos valores esperar para a opção.
  • Não use aspas na linha de comando 1
  • Não use type=list, pois ele retornará uma lista de listas
    • Isso acontece porque, sob o capô, argparseusa o valor de typepara coagir cada argumento que você escolheu type, e não o agregado de todos os argumentos.
    • Você pode usar type=int(ou o que for) para obter uma lista de entradas (ou o que for)

1 : Não quero dizer em geral. Quero dizer, usar aspas para passar uma listaargparse não é o que você deseja.

SethMMorton
fonte
3
Que tal uma lista de strings? Isso transforma vários argumentos de seqüência de caracteres ("wassup", "something" e "else") em uma lista de listas que se parece com isso: [['w', 'a', 's', 's', 'u' , 'p'], ['s', 'o', 'm', 'e', ​​'t', 'h', 'i', 'n', 'g'], ['e', ' l ',' s ',' e ']]
rd108
3
@ rd108 Entendo, aposto que você está usando a type=listopção. Não use isso. Isso transforma uma string em uma lista e, portanto, nas listas de listas.
SethMMorton
1
@Dror Todas as entradas são consideradas cadeias, a menos que você defina o typeparâmetro para outro objeto. Por padrão, esse método retorna uma lista de strings.
SethMMorton
1
--poderia dividir opções versus argumentos posicionais. prog --opt1 par1 ... -- posp1 posp2 ...
precisa saber é o seguinte
1
pode não ser intuitivo se houver argumentos posicionais, porque argparse não pode dizer o que deve ser um argumento posicional e o que pertence aos nargs . --ajuda a descobrir isso, como mostra o exemplo no meu comentário anterior. Suprimentos de usuário IOW --seguidos por todos os argumentos posicionais.
0andriy
83

Prefiro passar uma string delimitada que analiso posteriormente no script. As razões para isso são; a lista pode ser de qualquer tipo intou str, e às vezes usando nargsproblemas, se houver vários argumentos opcionais e argumentos posicionais.

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

Então,

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

ou,

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

vai funcionar bem. O delimitador também pode ser um espaço, o que aplicaria aspas ao redor do valor do argumento, como no exemplo da pergunta.

dojuba
fonte
57
Você pode definir o typeargumento para em lambda s: [int(time) for item in s.split(',')]vez de pós-processamento args.list.
chepner
13
@ Chepner, sim, você está absolutamente certo e seria mais pitônico - apenas um pequeno erro de digitação: int(time)deveria ser int(item). Meu exemplo foi uma versão simplificada do que normalmente faço, onde verifico muitas outras coisas, em vez de um simples processamento. Mas, para simplesmente responder à pergunta, também acho o seu caminho mais elegante. #
Dojuba
1
Esta resposta parece ser o mais pythônico
Quetzalcoatl
1
O comentário de @chepner é uma habilidade ninja séria +1 #
Briford Wylie
1
lambda items: list(csv.reader([items]))[0]com a biblioteca csv padrão é uma versão modificada do comentário do @chepner para qualquer pessoa preocupada com a entrada arbitrária de CSV (ref: answer from @adamk ).
Kevin
19

Além disso nargs, você pode usar choicesse souber a lista com antecedência:

>>> 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
fonte
10

Usando o parâmetro nargs no método add_argument do argparse

Eu uso nargs = ' ' como um parâmetro add_argument. Eu usei especificamente nargs = ' ' para a opção de escolher padrões se eu não estiver passando nenhum argumento explícito

Incluindo um trecho de código como exemplo:

Exemplo: temp_args1.py

Observe: O código de exemplo abaixo está escrito em python3. Alterando o formato da instrução print, pode ser executado em 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: Estou coletando vários argumentos de seqüência de caracteres que são armazenados na lista - opts.alist Se você quiser uma lista de números inteiros, altere o parâmetro type em parser.add_argument para int

Resultado da execução:

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
fonte
1
@ Py_minion Existe uma maneira de usar uma lista como argumento e ter a saída também como lista? temp_args1.py -i [item5 ,item6, item7]e ter a saída de sair como uma lista bem (em vez de lista aninhada)
Moondra
@Moondra Yes. feliz que você perguntou. `` `parser.add_argument ('- o', '--options', action = 'store', dest = 'opt_list', tipo = str, nargs = '*', padrão = sample_list, help =" Cadeia de bancos de dados separados por espaço em branco. Exemplos: \ -o opção1 opção2, -o opção3 ")` `` Aqui 'sample_list' é da lista de tipos com opções padrão. Ex: sample_list = [opção4, opção5]
Py_minion 1/11/17
1
@Py_minion Obrigado. Vai testá-lo hoje mais tarde.
Moondra
Eu usei isso, isso é muito útil para passar criando listas a partir dos argumentos.
Siby 28/05/19
5

Se você pretende fazer um único comutador usar vários parâmetros, use-o nargs='+'. Se o seu exemplo '-l' estiver usando números inteiros:

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

Produz

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

Se você especificar o mesmo argumento várias vezes, a ação padrão ( 'store') substituirá os dados existentes.

A alternativa é usar a appendação:

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

O que produz

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

Ou você pode escrever um manipulador / ação personalizado para analisar valores separados por vírgula, para que você possa executar

-l 123,234,345 -l 456
kfsone
fonte
5

In add_argument(), typeé apenas um objeto que pode ser chamado que recebe uma string e retorna o valor da opção.

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

Isso permitirá:

$ ./tool --list "[1,2,3,4]"
wonder.mice
fonte
Observe que, se alguém precisasse passar seqüências de caracteres, esse método exigiria que elas fossem citadas adequadamente na linha de comando. Um usuário pode achar isso inesperado. Se apenas analisar números inteiros, tudo bem.
SethMMorton 9/01/19
1

Se você possui uma lista aninhada em que as listas internas têm diferentes tipos e comprimentos e deseja preservar o tipo, por exemplo,

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

então você pode usar a solução proposta por @ sam-mason para esta pergunta , mostrada abaixo:

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

que dá:

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

Eu quero lidar com a passagem de várias listas, valores inteiros e seqüências de caracteres.

Link útil => Como passar uma variável Bash para 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:])

A ordem não é importante. Se você quiser passar uma lista, faça como entre "["e"] e separá-los usando uma vírgula.

Então,

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

Saída => ['my_string', '3', ['1', '2'], ['3', '4', '5']], my_argsvariável contém os argumentos em ordem.

alper
fonte
0

Eu acho que a solução mais elegante é passar uma função lambda para "type", como mencionado por Chepner. Além disso, se você não souber de antemão qual será o delimitador da sua lista, também poderá passar vários delimitadores para 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']
Nebulostic
fonte
Você quis dizer -lna chamada de exemplo? De onde -nveio?
Anthony
Além disso, a solução não funciona para mim no Python 3.8.2. Aqui está o código: parser.add_argument('-l', '--list', type = lambda s: re.split('[ ,;]', s)). Aqui é a entrada: script.py -l abc xyz, abc\nxyz. Finalmente, aqui está o resultado:script.py: error: unrecognized arguments: xyz, abcnxyz
Anthony
Mude o meu exemplo para que funcione :)
Nebulostic