2011-10-02 3 views
15

Sto provando a impostare un dizionario come argomento opzionale (usando argparse); la riga seguente è quello che ho finora:type = dict in argparse.add_argument()

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\'}).') 

Ma l'esecuzione dello script:

$ ./script.py -i {'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'} 

script.py: error: argument -i/--image: invalid dict value: '{name:' 

Anche se, all'interno del interprete,

>>> a={'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'} 

funziona bene.

Quindi, come devo passare invece l'argomento? Grazie in anticipo.

+0

Si può leggere formati come JSON da un file esterno o stdin, e quindi analizzarlo. Quindi il tipo argparse sarà in realtà un file. –

+0

come @wim ha detto nella sua risposta che la shell sta elaborando gli argomenti prima di passarli a python. Se anteponi il comando con 'echo' ('echo ./script.py -i {' nome ': ...') vedrai cosa sta vedendo python (principalmente non sta ricevendo citazioni). Nel tuo caso che non ci sono '$' nel tuo param (che potrebbe essere interpretato dalla shell come una variabile ambientale) puoi circondare il tuo dict con virgolette doppie: './script.py -i" {'name': ' img.png ', ....} "' –

risposta

4

Scommetto che il tuo guscio è fu> < 0ring le parentesi graffe (vedi here). Vorrei semplicemente passare gli argomenti in uno a uno a CLI, usando Argument groups e creare il dict a livello di codice.

Passare in un complicato oggetto python come un dizionario, costringendo l'utente a conoscere la sintassi python, mi sembra abbastanza fugace.

0

Si potrebbe provare:

$ ./script.py -i "{'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'}" 

non ho ancora testato questo, sul mio telefono in questo momento.

Modifica: BTW Sono d'accordo con @wim, penso che avere ogni kv del dict come argomento sarebbe più bello per l'utente.

1

Si può sicuramente ottenere in qualcosa che assomiglia a un dizionario letterale nel parser argomento, ma hai avuto modo di citarlo in modo che quando la shell analizza la linea di comando, si tratta come

  • un singolo argomento invece di molti (il carattere di spazio è il normale delimitatore argomento)
  • correttamente citato (il guscio rimuove citazioni durante l'analisi, perché li sta usando per il raggruppamento)

Quindi qualcosa di simile può ottenere il testo che volevi nella tua p rogramma:

python MYSCRIPT.py -i "{\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\"}" 

Tuttavia, questa stringa non è un argomento valido al costruttore dict; invece, è uno snippet di codice Python valido. Si potrebbe dire il parser argomento che il "tipo" di questo argomento è eval, e che funzionerà:

import argparse 

parser = argparse.ArgumentParser() 
parser.add_argument('-i','--image', type=eval, help='Generate an image map...') 
args = parser.parse_args() 
print args 

e chiamandolo:

% python MYSCRIPT.py -i "{\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\"}" 
Namespace(image={'0': '#ff00ff00', '100%': '#f80654ff', 'voids': '#00ff00ff', 'name': 'img.png'}) 

Ma questo non è sicuro; l'input potrebbe essere qualsiasi cosa e stai valutando un codice arbitrario. Sarebbe altrettanto ingombrante, ma il seguente sarebbe molto più sicuro:

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 

funziona questo anche, ma è molto più restrittiva su ciò che permetterà di essere eval 'd.

Tuttavia, è molto complicato avere l'utente digitare qualcosa, correttamente citato, che assomiglia ad un dizionario python sulla riga di comando. E dovresti fare dei controlli dopo aver fatto in modo che passassero in un dizionario invece che in qualcos'altro che fossero valutabili e avessero le chiavi giuste. Molto più facile da utilizzare se:

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 

Per:

% 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'} 
+0

Wow, grazie per la panoramica delle possibilità; tuttavia, nonostante nell'esempio ho inserito solo 0 e 100%, questi potrebbero in realtà essere qualsiasi valore (ad esempio {'46% ':' # 0f0e0d0c ',' 3629 ',' # f0e0d0c0 '}), che non è contemplato nel tuo ultimo pezzo di codice ... – user975296

37

Necroing questo: json.loads funziona anche qui. Non sembra troppo sporco.

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) 

Returns:

{u'0': u'#ff00ff00', u'100%': u'#f80654ff', u'voids': u'#00ff00ff', u'name': u'img.png'}

+0

'json.loads' è una buona scelta per' type'. Come 'int' e' float' prende una stringa e restituisce un 'ValueError' se non può gestirlo. È anche più sicuro di "eval". A tale scopo può essere un po 'troppo generale (cioè può gestire una lista '' [1, 2] ''), ma l'utente può occuparsene dopo il 'parse_args()'. – hpaulj

+0

Quando i valori sono 'str'ings,' int', 'float', funziona correttamente. Per altri tipi di valori, come 'bool', non lo fa (ma passare' 1' per 'True' dovrebbe funzionare ma tutto il codice ben scritto). – gerrit

1

Uno dei modi più semplici che ho trovato è quello di analizzare il dizionario come un elenco, e poi convertire in un dizionario. Ad esempio, utilizzando 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) 

quindi è possibile digitare sulla riga di comando qualcosa come:

./script.py -i name img.png voids '#00ff00ff' 0 '#ff00ff00' '100%' '#f80654ff' 

per ottenere il risultato desiderato:

Namespace(image={'name': 'img.png', '0': '#ff00ff00', 'voids': '#00ff00ff', '100%': '#f80654ff'}) 
7

Per completezza, e in modo simile a JSON .loads, è possibile utilizzare yaml.load (disponibile da PyYAML in PyPI). Questo ha il vantaggio su json in quanto non è necessario citare chiavi e valori individuali sulla riga di comando a meno che non si stia tentando, per esempio, di forzare gli interi in stringhe o altrimenti di superare la semantica della conversione di yaml. Ma ovviamente l'intera stringa avrà bisogno di essere citata in quanto contiene spazi!

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

Anche se certamente nella questione data, molte delle chiavi e valori avrebbero dovuto essere citato o riformulato per evitare YAML da vomitare. Utilizzando i seguenti dati sembra funzionare abbastanza bene, se avete bisogno numerico piuttosto che valori di stringa:

>>> 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} 
+0

Corretti (mancanza) di chiamate parser.parse_args. Grazie per avermelo fatto notare, Hotschke – Hamish

0

Consigli generali: NON USARE eval.

Se proprio devi ... "eval" è pericoloso. Usalo se sei sicuro che nessuno introdurrà intenzionalmente input malevoli. Anche allora ci possono essere degli svantaggi. Ho coperto un cattivo esempio.

L'utilizzo di eval invece di json.loads presenta alcuni vantaggi. Un ditt non ha davvero bisogno di essere un json valido. Quindi, la valutazione può essere piuttosto indulgente nell'accettare "dizionari". Possiamo occuparci della parte "pericolosa" assicurandoci che il risultato finale sia effettivamente un dizionario 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) 

uscita:

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'}) 
+0

Questo è un consiglio estremamente pericoloso. L'uso di eval (ovviamente) causerà l'input dalla cmdline per essere valutato come python. Ciò accade * prima * restituisce un valore in modo che il controllo del tipo sia troppo poco o troppo tardi. Inoltre, esiste un sacco di python pericoloso valido che restituirà ancora un dict .... La dichiarazione "possiamo prenderci cura del pericolo" è sia imprecisa che pericolosa da diffondere come consiglio. –

+0

Ok. Essere d'accordo. Sto cambiando la lingua della risposta. –