multiple - python parser example
type=dict en argparse.add_argument() (7)
Estoy tratando de configurar un diccionario como argumento opcional (usando argparse); La siguiente línea es la que tengo hasta ahora:
parser.add_argument(''-i'',''--image'', type=dict, help=''Generate an image map from the input file (syntax: {/'name/': <name>, /'voids/': /'#08080808/', /'0/': /'#00ff00ff/', /'100%%/': /'#ff00ff00/'}).'')
Pero ejecutando el script:
$ ./script.py -i {''name'': ''img.png'',''voids'': ''#00ff00ff'',''0'': ''#ff00ff00'',''100%'': ''#f80654ff''}
script.py: error: argument -i/--image: invalid dict value: ''{name:''
Aunque, dentro del intérprete,
>>> a={''name'': ''img.png'',''voids'': ''#00ff00ff'',''0'': ''#ff00ff00'',''100%'': ''#f80654ff''}
funciona bien
Entonces, ¿cómo debo pasar el argumento en su lugar? Gracias por adelantado.
Apuesto a que tu concha es fu> <0ring las llaves (ver here ). Simplemente pasaría los argumentos en uno por uno en la CLI, usando grupos de argumentos , y compilaría el dict por programación.
Pasar un objeto python complicado como un diccionario, obligando al usuario a conocer la sintaxis de python, me parece un poco feo.
Consejo general: NO UTILICE eval.
Si realmente tienes que ... "eval" es peligroso. Úselo si está seguro de que nadie ingresará intencionalmente información maliciosa. Incluso entonces puede haber desventajas. He cubierto un mal ejemplo.
Sin embargo, usar eval en lugar de json.loads también tiene algunas ventajas. Un dict no necesita ser un json válido. Por lo tanto, eval puede ser bastante indulgente al aceptar "diccionarios". Podemos ocuparnos de la parte del "peligro" asegurándonos de que el resultado final sea un diccionario de Python.
import json
import argparse
tests = [
''{"name": "img.png","voids": "#00ff00ff","0": "#ff00ff00","100%": "#f80654ff"}'',
''{"a": 1}'',
"{''b'':1}",
"{''$abc'': ''$123''}",
''{"a": "a" "b"}'' # Bad dictionary but still accepted by eval
]
def eval_json(x):
dicti = eval(x)
assert isinstance(dicti, dict)
return dicti
parser = argparse.ArgumentParser()
parser.add_argument(''-i'', ''--input'', type=eval_json)
for test in tests:
args = parser.parse_args([''-i'', test])
print(args)
Salida:
Namespace(input={''name'': ''img.png'', ''0'': ''#ff00ff00'', ''100%'': ''#f80654ff'', ''voids'': ''#00ff00ff''})
Namespace(input={''a'': 1})
Namespace(input={''b'': 1})
Namespace(input={''$abc'': ''$123''})
Namespace(input={''a'': ''ab''})
Definitivamente, puede obtener algo que parece un literal de diccionario en el analizador de argumentos, pero debe citarlo para que cuando el shell analice su línea de comando, aparezca como
- un solo argumento en lugar de muchos (el carácter de espacio es el delimitador de argumento normal)
- citado correctamente (el shell elimina las comillas durante el análisis, porque las usa para agrupar)
Entonces, algo como esto puede incluir el texto que desea en su programa:
python MYSCRIPT.py -i "{/"name/": /"img.png/", /"voids/": /"#00ff00ff/",/"0/": /"#ff00ff00/",/"100%/": /"#f80654ff/"}"
Sin embargo, esta cadena no es un argumento válido para el constructor dict; en su lugar, es un fragmento de código de python válido. Podría decirle a su analizador de argumentos que el "tipo" de este argumento es eval
, y que funcionará:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(''-i'',''--image'', type=eval, help=''Generate an image map...'')
args = parser.parse_args()
print args
y llamándolo:
% python MYSCRIPT.py -i "{/"name/": /"img.png/", /"voids/": /"#00ff00ff/",/"0/": /"#ff00ff00/",/"100%/": /"#f80654ff/"}"
Namespace(image={''0'': ''#ff00ff00'', ''100%'': ''#f80654ff'', ''voids'': ''#00ff00ff'', ''name'': ''img.png''})
Pero esto no es seguro; la entrada podría ser cualquier cosa, y estás evaluando un código arbitrario. Sería igualmente difícil de manejar, pero lo siguiente sería mucho más seguro:
import argparse
import ast
parser = argparse.ArgumentParser()
parser.add_argument(''-i'',''--image'', type=ast.literal_eval, help=''Generate an image map...'')
args = parser.parse_args()
print args
Esto también funciona, pero es MUCHO más restrictivo en cuanto a lo que permitirá ser eval
.
Sin embargo, es muy difícil que el usuario escriba algo, correctamente citado, que parezca un diccionario de python en la línea de comandos. Y, tendría que hacer algunas comprobaciones después del hecho para asegurarse de que pasaron en un diccionario en lugar de otra cosa que se pueda evaluar y que tuvieran las claves correctas. Mucho más fácil de usar si:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--image-name", required=True)
parser.add_argument("--void-color", required=True)
parser.add_argument("--zero-color", required=True)
parser.add_argument("--full-color", required=True)
args = parser.parse_args()
image = {
"name": args.image_name,
"voids": args.void_color,
"0%": args.zero_color,
"100%": args.full_color
}
print image
Por:
% python MYSCRIPT.py --image-name img.png --void-color /#00ff00ff --zero-color /#ff00ff00 --full-color /#f80654ff
{''100%'': ''#f80654ff'', ''voids'': ''#00ff00ff'', ''name'': ''img.png'', ''0%'': ''#ff00ff00''}
Necroing esto: json.loads
funciona aquí. No parece demasiado sucio.
import json
import argparse
test = ''{"name": "img.png","voids": "#00ff00ff","0": "#ff00ff00","100%": "#f80654ff"}''
parser = argparse.ArgumentParser()
parser.add_argument(''-i'', ''--input'', type=json.loads)
args = parser.parse_args([''-i'', test])
print(args.input)
Devoluciones:
{u''0'': u''#ff00ff00'', u''100%'': u''#f80654ff'', u''voids'': u''#00ff00ff'', u''name'': u''img.png''}
Para completar, y de manera similar a json.loads, puede usar yaml.load (disponible en PyYAML en PyPI). Esto tiene la ventaja sobre json en que no es necesario citar claves y valores individuales en la línea de comando a menos que esté tratando, por ejemplo, de forzar enteros en cadenas o superar la semántica de conversión de yaml. ¡Pero obviamente toda la cadena necesitará una cita ya que contiene espacios!
>>> import argparse
>>> import yaml
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument(''-fna'', ''--filename-arguments'', type=yaml.load)
>>> data = "{location: warehouse A, site: Gloucester Business Village}"
>>> ans = parser.parse_args([''-fna'', data])
>>> print ans.filename_arguments[''site'']
Gloucester Business Village
Aunque hay que admitirlo en la pregunta dada, muchas de las claves y valores deberían citarse o reformularse para evitar que el yaml barfing. El uso de los siguientes datos parece funcionar bastante bien, si necesita valores numéricos en lugar de cadenas:
>>> parser.add_argument(''-i'', ''--image'', type=yaml.load)
>>> data = "{name: img.png, voids: 0x00ff00ff, ''0%'': 0xff00ff00, ''100%'': 0xf80654ff}"
>>> ans = parser.parse_args([''-i'', data])
>>> print ans.image
{''100%'': 4161164543L, ''voids'': 16711935, ''name'': ''img.png'', ''0%'': 4278255360L}
Tu podrías intentar:
$ ./script.py -i "{''name'': ''img.png'',''voids'': ''#00ff00ff'',''0'': ''#ff00ff00'',''100%'': ''#f80654ff''}"
No he probado esto, en mi teléfono en este momento.
Edit: BTW Estoy de acuerdo con @wim, creo que tener cada kv del dict como argumento sería mejor para el usuario.
Una de las formas más simples que he encontrado es analizar el diccionario como una lista y luego convertirlo en un diccionario. Por ejemplo utilizando Python3:
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(''-i'', ''--image'', type=str, nargs=''+'')
args = parser.parse_args()
if args.image is not None:
i = iter(args.image)
args.image = dict(zip(i, i))
print(args)
entonces puede escribir en la línea de comando algo como:
./script.py -i name img.png voids ''#00ff00ff'' 0 ''#ff00ff00'' ''100%'' ''#f80654ff''
para obtener el resultado deseado:
Namespace(image={''name'': ''img.png'', ''0'': ''#ff00ff00'', ''voids'': ''#00ff00ff'', ''100%'': ''#f80654ff''})