Usando la misma opción varias veces en argparse de Python

82

Estoy tratando de escribir un script que acepte múltiples fuentes de entrada y haga algo con cada una. Algo como esto

./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

Pero no puedo entender cómo hacer esto usando argparse. Parece que está configurado para que cada indicador de opción solo se pueda usar una vez. Sé cómo asociar varios argumentos con una sola opción ( nargs='*'o nargs='+'), pero eso aún no me permite usar la -ibandera varias veces. ¿Cómo hago para lograr esto?

Para que quede claro, lo que me gustaría al final es una lista de listas de cadenas. Entonces

[["input1_url", "input1_name", "input1_other"],
 ["input2_url", "input2_name", "input2_other"],
 ["input3_url", "input3_name"]]
John Allard
fuente
Entonces, ¿por qué no asociar los múltiples argumentos de la fuente de entrada con esa única opción?
TigerhawkT3
Porque cada una de las múltiples fuentes de entrada también necesita tener múltiples argumentos de cadena. Me gustaría tener que usar el indicador -i para cada una de las entradas, y cada entrada contendría todas las cadenas entre los indicadores -i sucesivos. Quiero que funcione como ffmpeg donde especificas las entradas con -i
John Allard

Respuestas:

96

Aquí hay un analizador que maneja un argumento 2 repetido opcional, con nombres definidos en 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']])

Esto no maneja el 2 or 3 argumentcaso (aunque escribí un parche hace algún tiempo para un error / problema de Python que manejaría tal rango).

¿Qué tal una definición de argumento separada con nargs=3y metavar=('url','name','other')?

La tupla metavartambién se puede utilizar con nargs='+'y nargs='*'; las 2 cadenas se utilizan como [-u A [B ...]]o [-u [A [B ...]]].

hpaulj
fuente
1
¡Wow genial! Me encanta cómo la función de ayuda muestra lo que representan los componentes individuales de la opción de varias partes. ¡Usaré esto!
John Allard
48

Esto es simple; simplemente agregue ambos action='append'y nargs='*'(o '+').

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

Luego, cuando lo ejecutas, obtienes

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
fuente
2
Gracias, ¡exactamente lo que necesitaba! : D Nota al margen: un posible valor predeterminado debe ser el tipo lista / matriz, o Argparse fallará
Tarwin
22

-idebe configurarse para aceptar 3 argumentos y utilizar la appendacción.

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

Para manejar un valor opcional, puede intentar usar un tipo personalizado simple. En este caso, el argumento de -ies una única cadena delimitada por comas, con el número de divisiones limitado a 2. Debería procesar posteriormente los valores para asegurarse de que haya al menos dos valores especificados.

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

Para mayor control, defina una acción personalizada. Este extiende el incorporado _AppendAction(usado por action='append'), pero solo hace una verificación de rango en el número de argumentos dados -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
fuente
1
¡Brillante! Gracias por tu ayuda.
John Allard
2
Esto no hace exactamente lo que quieres; Me perdí que inputX_other_vares opcional.
chepner
Acabo de volver para comentar como tal, tu camino requiere todas las vars. ¡Sin embargo, está en la dirección correcta!
John Allard
1
OK, actualizado con un par de opciones para manejar un tercer argumento opcional.
chepner