¿Cómo puedo pasar una lista como argumento de línea de comandos con argparse?

441

Estoy tratando de pasar una lista como argumento a un programa de línea de comandos. ¿Hay una argparseopción para pasar una lista como opción?

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

La secuencia de comandos se llama como a continuación

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

Respuestas:

879

TL; DR

Use la nargsopción o la 'append'configuración de la actionopción (dependiendo de cómo desee que se comporte la interfaz de usuario).

nargs

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

nargs='+'toma 1 o más argumentos, nargs='*'toma cero o más.

adjuntar

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 appendusted proporciona la opción varias veces para construir la lista.

¡No lo uses type=list! - Probablemente no hay situación en la que se desea utilizar type=listcon argparse. Siempre.


Echemos un vistazo con más detalle a algunas de las diferentes formas en que uno podría intentar hacer esto, y el 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)

Aquí está la salida que puede 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']

Comida para llevar :

  • Use nargsoaction='append'
    • nargspuede ser más directo desde la perspectiva del usuario, pero puede no ser intuitivo si hay argumentos posicionales porque argparseno se puede decir qué debería ser un argumento posicional y qué pertenece al nargs; Si tiene argumentos posicionales, action='append'puede terminar siendo una mejor opción.
    • Lo anterior sólo es cierto si nargsse da '*', '+'o '?'. Si proporciona un número entero (como 4), no habrá problemas para mezclar opciones nargsy argumentos posicionales porque argparsesabrá exactamente cuántos valores esperar para la opción.
  • No use comillas en la línea de comando 1
  • No lo use type=list, ya que devolverá una lista de listas
    • Esto sucede porque debajo del capó argparseusa el valor de typepara coaccionar cada argumento dado individual que haya elegido type, no el agregado de todos los argumentos.
    • Puede usar type=int(o lo que sea) para obtener una lista de entradas (o lo que sea)

1 : No me refiero en general. Me refiero a que usar comillas para pasar una listaargparse no es lo que quieres.

SethMMorton
fuente
3
¿Qué pasa con una lista de cadenas? Esto convierte múltiples argumentos de cadena ("wassup", "something" y "else") en una lista de listas que se ve así: [['' w ',' a ',' s ',' s ',' u ' , 'p'], ['s', 'o', 'm', 'e', ​​'t', 'h', 'i', 'n', 'g'], ['e', ' l ',' s ',' e ']]
rd108
3
@ rd108 Ya veo, apuesto a que está utilizando la type=listopción. No uses eso. Eso convierte una cadena en una lista y, por lo tanto, las listas de listas.
SethMMorton
1
@Dror Se supone que todas las entradas son cadenas a menos que establezca el typeparámetro en algún otro objeto. Por defecto, este método devuelve una lista de cadenas.
SethMMorton
1
--podría dividir opciones frente a argumentos posicionales. prog --opt1 par1 ... -- posp1 posp2 ...
0andriy
1
puede no ser intuitivo si hay argumentos posicionales porque argparse no puede decir qué debería ser un argumento posicional y qué pertenece a los nargs . --ayuda a resolver esto como se muestra en el ejemplo en mi comentario anterior. Suministros de usuario IOW --seguidos de todos los argumentos posicionales.
0andriy
83

Prefiero pasar una cadena delimitada que analizo más adelante en el script. Las razones para esto son; la lista puede ser de cualquier tipo into str, y a veces usando nargsme encuentro con problemas si hay múltiples argumentos opcionales y argumentos posicionales.

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

Entonces,

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

o,

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

Funcionará bien. El delimitador también puede ser un espacio, que sin embargo impondría comillas alrededor del valor del argumento como en el ejemplo de la pregunta.

dojuba
fuente
57
Puede establecer el typeargumento en lambda s: [int(time) for item in s.split(',')]lugar de postprocesamiento args.list.
chepner
13
@ Chepner, sí, tienes toda la razón y sería más pitónico, solo un pequeño error tipográfico: int(time)debería serlo int(item). Mi ejemplo fue una versión simplificada de lo que normalmente hago, donde verifico muchas otras cosas en lugar de un simple procesamiento. Pero simplemente para responder a la pregunta, yo también encuentro tu camino más elegante ..
dojuba
1
esta respuesta parece ser la más pitónica
Quetzalcóatl
1
El comentario de @chepner es algunas habilidades ninja serias +1
Briford Wylie
1
lambda items: list(csv.reader([items]))[0]con la biblioteca csv estándar hay una versión modificada del comentario de @chepner para cualquier persona preocupada por la entrada arbitraria de CSV (ref: respuesta de @adamk ).
Kevin
19

Además nargs, es posible que desee utilizar choicessi conoce la lista de antemano:

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

Usar el parámetro nargs en el método add_argument de argparse

Yo uso nargs = ' ' como un parámetro add_argument. Usé específicamente nargs = ' ' para la opción de elegir valores predeterminados si no estoy pasando ningún argumento explícito

Incluyendo un fragmento de código como ejemplo:

Ejemplo: temp_args1.py

Tenga en cuenta: El siguiente código de muestra está escrito en python3. Al cambiar el formato de la declaración de impresión, puede ejecutarse en 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: estoy recopilando múltiples argumentos de cadena que se almacenan en la lista - opts.alist Si desea una lista de enteros, cambie el parámetro de tipo en parser.add_argument a int

Resultado de ejecución:

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
fuente
1
@Py_minion ¿Hay alguna manera de usar una lista como argumento y tener también la salida como lista? temp_args1.py -i [item5 ,item6, item7]y que la salida salga también como una lista (en lugar de una lista anidada)
Moondra
@Moondra Sí. Me alegra que hayas preguntado. `` `parser.add_argument ('- o', '--options', action = 'store', dest = 'opt_list', type = str, nargs = '*', default = sample_list, help =" Cadena de bases de datos separados por espacios en blanco. Ejemplos: \ -o opción1 opción2, -o opción3 ")` `` Aquí 'muestra_lista' es de tipo lista con opciones predeterminadas. Ej: sample_list = [opción4, opción5]
Py_minion
1
@Py_minion Gracias. Voy a probarlo más tarde hoy.
Moondra
Usé esto, esto es muy útil para pasar la creación de listas a partir de los argumentos.
Siby
5

Si tiene la intención de hacer que un solo interruptor tome múltiples parámetros, entonces los usa nargs='+'. Si su ejemplo '-l' en realidad está tomando enteros:

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!

Si especifica el mismo argumento varias veces, la acción predeterminada ( 'store') reemplaza los datos existentes.

La alternativa es usar la appendacción:

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

Que produce

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

O puede escribir un controlador / acción personalizado para analizar valores separados por comas para que pueda hacer

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

En add_argument(), typees solo un objeto invocable que recibe una cadena y devuelve el valor de la opción.

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

Esto permitirá:

$ ./tool --list "[1,2,3,4]"
wonder.mice
fuente
Tenga en cuenta que si uno necesita pasar cadenas, este método requeriría que las citen apropiadamente en la línea de comando. Un usuario puede encontrar esto inesperado. Si solo analiza números enteros, está bien.
SethMMorton
1

Si tiene una lista anidada donde las listas internas tienen diferentes tipos y longitudes y desea conservar el tipo, por ejemplo,

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

entonces puede usar la solución propuesta por @ sam-mason para esta pregunta , que se muestra a continuación:

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

lo que da:

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

Quiero manejar pasar múltiples listas, valores enteros y cadenas.

Enlace útil => ¿Cómo pasar una variable 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:])

El orden no es importante. Si desea pasar una lista acaba de hacer como en el medio "["y "]y separarlos mediante una coma.

Entonces,

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

Salida => ['my_string', '3', ['1', '2'], ['3', '4', '5']], la my_argsvariable contiene los argumentos en orden.

alper
fuente
0

Creo que la solución más elegante es pasar una función lambda a "tipo", como lo menciona Chepner. Además de esto, si no sabe de antemano cuál será el delimitador de su lista, también puede pasar varios 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']
Nebulosa
fuente
¿Querías decir -len la llamada de ejemplo? ¿De donde -nvino?
Anthony
Además, la solución no me funciona en Python 3.8.2. Aquí está el código: parser.add_argument('-l', '--list', type = lambda s: re.split('[ ,;]', s)). Aquí está la entrada: script.py -l abc xyz, abc\nxyz. Finalmente, aquí está el resultado:script.py: error: unrecognized arguments: xyz, abcnxyz
Anthony
Cambie mi ejemplo para que funcione :)
Nebulastic