可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying to set up a dictionary as optional argument (using argparse); the following line is what I have so far:
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\'}).')
But running the script:
$ ./script.py -i {'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'} script.py: error: argument -i/--image: invalid dict value: '{name:'
Even though, inside the interpreter,
>>> a={'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'}
works just fine.
So how should I pass the argument instead? Thanks in advance.
回答1:
Necroing this: json.loads
works here, too. It doesn't seem too dirty.
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'}
回答2:
For completeness, and similarly to json.loads, you could use yaml.load (available from PyYAML in PyPI). This has the advantage over json in that there is no need to quote individual keys and values on the command line unless you are trying to, say, force integers into strings or otherwise overcome yaml conversion semantics. But obviously the whole string will need quoting as it contains spaces!
>>> 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
Although admittedly in the question given, many of the keys and values would have to be quoted or rephrased to prevent yaml from barfing. Using the following data seems to work quite nicely, if you need numeric rather than string values:
>>> 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}
回答3:
I bet your shell is fu><0ring the braces (see here). I would just pass the args in one-by-one at CLI, using Argument groups, and build the dict programmatically.
Passing in a complicated python object like a dictionary, forcing the user to know python syntax, seems kinda fugly to me.
回答4:
You can definitely get in something that looks like a dictionary literal into the argument parser, but you've got to quote it so when the shell parses your command line, it comes in as
- a single argument instead of many (the space character is the normal argument delimiter)
- properly quoted (the shell removes quotes during parsing, because it's using them for grouping)
So something like this can get the text you wanted into your program:
python MYSCRIPT.py -i "{\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\"}"
However, this string is not a valid argument to the dict constructor; instead, it's a valid python code snippet. You could tell your argument parser that the "type" of this argument is eval
, and that will work:
import argparse parser = argparse.ArgumentParser() parser.add_argument('-i','--image', type=eval, help='Generate an image map...') args = parser.parse_args() print args
and calling it:
% python MYSCRIPT.py -i "{\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\"}" Namespace(image={'0': '#ff00ff00', '100%': '#f80654ff', 'voids': '#00ff00ff', 'name': 'img.png'})
But this is not safe; the input could be anything, and you're evaluating arbitrary code. It would be equally unwieldy, but the following would be much safer:
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
This also works, but is MUCH more restrictive on what it will allow to be eval
'd.
Still, it's very unwieldy to have the user type out something, properly quoted, that looks like a python dictionary on the command line. And, you'd have to do some checking after the fact to make sure they passed in a dictionary instead of something else eval-able, and had the right keys in it. Much easier to use if:
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
For:
% 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'}
回答5:
One of the simplest ways I've found is to parse the dictionary as a list, and then convert that to a dictionary. For example using 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)
then you can type on the command line something like:
./script.py -i name img.png voids '#00ff00ff' 0 '#ff00ff00' '100%' '#f80654ff'
to get the desired result:
Namespace(image={'name': 'img.png', '0': '#ff00ff00', 'voids': '#00ff00ff', '100%': '#f80654ff'})
回答6:
$ ./script.py -i "{'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'}"
I haven't tested this, on my phone right now.
Edit: BTW I agree with @wim, I think having each kv of the dict as an argument would be nicer for the user.
回答7:
General Advice: DO NOT USE eval.
If you really have to ... "eval" is dangerous. Use it if you are sure no one will knowingly input malicious input. Even then there can be disadvantages. I have covered one bad example.
Using eval instead of json.loads has some advantages as well though. A dict doesn't really need to be a valid json. Hence, eval can be pretty lenient in accepting "dictionaries". We can take care of the "danger" part by making sure that final result is indeed a python dictionary.
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)
Output:
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'})