34

I want to receive a dict(str -> str) argument from the command line. Does argparse.ArgumentParser provide it? Or any other library?

For the command line:

program.py --dict d --key key1 --value val1 --key key2 --value val2

I expect the following dictionary:

d = {"key1": "val1", "key2": "val2"}
orion_tvv
  • 1,681
  • 4
  • 17
  • 29
  • 1
    Are you doing argument parsing or what? Collecting it to a dict doesn't seem really meaningful. – ljk321 May 01 '15 at 12:00
  • 1
    @skyline75489. Yes. I parsing the **d** argument. Why lists can be arguments, and dictionaries can not.. – orion_tvv May 01 '15 at 12:07
  • 1
    Arguments can be very complex. It can be optional or required, positional or non-positional, need-value or do-not-need-value. I'm thinking adding `dict` support would make things even more complicated. – ljk321 May 01 '15 at 12:12
  • How about [this approach](http://stackoverflow.com/questions/29335145/python-argparse-extra-args/29335524#29335524)? – Antti Haapala -- Слава Україні May 01 '15 at 12:39
  • 1
    wouldn't it be better as `program.py --opt1 k1=v1,k2=v2,k3=v3`? – shx2 May 01 '15 at 12:42
  • @AnttiHaapala This is good. Kind of similar to what I did in the answer below. – ljk321 May 01 '15 at 12:44
  • @AnttiHaapala - I like that approach. I wish it would've worked for my use case when I had to do essentially the same thing as the OP is trying to do here. – Deacon May 01 '15 at 13:57
  • 1
    Another option is to input the values as a (quoted) JSON string. – hpaulj May 01 '15 at 18:42

9 Answers9

46

Here's another solution using a custom action, if you want to specify dict key pairs together comma-separated --

import argparse
import sys
parser = argparse.ArgumentParser(description='parse key pairs into a dictionary')

class StoreDictKeyPair(argparse.Action):
     def __call__(self, parser, namespace, values, option_string=None):
         my_dict = {}
         for kv in values.split(","):
             k,v = kv.split("=")
             my_dict[k] = v
         setattr(namespace, self.dest, my_dict)

parser.add_argument("--key_pairs", dest="my_dict", action=StoreDictKeyPair, metavar="KEY1=VAL1,KEY2=VAL2...")

args = parser.parse_args(sys.argv[1:])
print args

Running:

python parse_kv.py --key_pairs 1=2,a=bbb,c=4 --key_pairs test=7,foo=bar

Output:

Namespace(my_dict={'1': '2', 'a': 'bbb', 'c': '4', 'test': '7', 'foo': 'bar'})

If you want to use nargs instead of comma-separated values in string:

class StoreDictKeyPair(argparse.Action):
     def __init__(self, option_strings, dest, nargs=None, **kwargs):
         self._nargs = nargs
         super(StoreDictKeyPair, self).__init__(option_strings, dest, nargs=nargs, **kwargs)
     def __call__(self, parser, namespace, values, option_string=None):
         my_dict = {}
         print "values: {}".format(values)
         for kv in values:
             k,v = kv.split("=")
             my_dict[k] = v
         setattr(namespace, self.dest, my_dict)

parser.add_argument("--key_pairs", dest="my_dict", action=StoreDictKeyPair, nargs="+", metavar="KEY=VAL")

args = parser.parse_args(sys.argv[1:])
print args

Running

python arg_test4.py --key_pairs 1=2 a=bbb c=4 test=7 foo=bar

Outputs:

values: ['1=2', 'a=bbb', 'c=4', 'test=7', 'foo=bar']
Namespace(my_dict={'1': '2', 'a': 'bbb', 'c': '4', 'test': '7', 'foo': 'bar'})
Roman Kovtuh
  • 561
  • 8
  • 14
storm_m2138
  • 2,281
  • 2
  • 20
  • 18
  • 1
    This is definitely the best answer – mgalardini Jun 14 '17 at 09:40
  • Undervalued answer. Alas, I've already given this all the love I have to give. – The Nate Jan 28 '18 at 08:01
  • 2
    Great answer (already upvoted) but this doesn't parse boolean strings as boolean or other data types (list, dict) passed as strings. Need to eval the value. Just a heads up ! – skyfail Aug 11 '21 at 17:46
  • 1
    I used `my_dict[k] = ast.literal_eval(v)` to implement the type cast – eqzx Jun 21 '22 at 19:10
  • Under the `__call__` method I had to use `my_dict = getattr(namespace, self.dest, {})` to get this to work after I switched to using subparsers, and tyvm, this is great – Daniel Griffin Aug 31 '22 at 21:29
22

I would use something like this:

p = argparse.ArgumentParser()
p.add_argument("--keyvalue", action='append',
               type=lambda kv: kv.split("="), dest='keyvalues')

args = p.parse_args("--keyvalue foo=6 --keyvalue bar=baz".split())
d = dict(args.keyvalues)

You could create a custom action which would "append" a parsed key-value pair directly into a dictionary, rather than simply accumulating a list of (key, value) tuples. (Which I see is what skyline75489 did; my answer differs in using a single --keyvalue option with a custom type instead of separate --key and --value options to specify pairs.)

chepner
  • 497,756
  • 71
  • 530
  • 681
10

just another easy way:

parser = argparse.ArgumentParser()
parser.add_argument('--key1')
parser.add_argument('--key2')
args = parser.parse_args()
my_dict = args.__dict__
Clemens69
  • 125
  • 1
  • 3
  • 4
    No, I don't want to convert agruments into dictionary. It can be easily done: `arguments = vars(parser.parse_args())`. Please reread question with example – orion_tvv Jul 09 '19 at 12:39
  • It's generally not a great practice to access the `__dict__` property of a class you don't control as it can contain metadata you aren't expecting. – jtschoonhoven Apr 16 '21 at 21:20
5

use vars

d = vars(parser.parse_args())
范晔斌
  • 51
  • 1
  • 1
  • Thank you for the contribution. But your code doesn't solve the problem. It just convert all passed arguments into dictionary, but code should also work with unknown dynamic keys. – orion_tvv Jun 25 '20 at 10:16
3

Python receives arguments in the form of an array argv. You can use this to create the dictionary in the program itself.

import sys
my_dict = {}
for arg in sys.argv[1:]:
    key, val=arg.split(':')[0], arg.split(':')[1]
    my_dict[key]=val

print my_dict

For command line:

python program.py key1:val1 key2:val2 key3:val3

Output:

my_dict = {'key3': 'val3', 'key2': 'val2', 'key1': 'val1'}

Note: args will be in string, so you will have to convert them to store numeric values.

I hope it helps.

divyum
  • 1,286
  • 13
  • 20
  • I think your solution is ok, but it is little dirty. 1. keys and values can't contains ':' 2. too hard to control the order of multiple arguments – orion_tvv May 01 '15 at 12:16
  • 1
    what I gave is not the only solution, the point is you have a list of arguments - ['key1', 'val1', 'key2', 'val2', 'key3', 'val3'], so can obtain dict with any logic, eg: every even element (say pos 'i') is the key and value for it will be at pos (i+1), so just run the loop with a step size of 2 and store the dictionary. – divyum May 01 '15 at 14:06
3

Python one-line argparse dictionary arguments argparse_dictionary.py

# $ python argparse_dictionary.py --arg_dict=1=11,2=22;3=33 --arg_dict=a=,b,c=cc,=dd,=ee=,
# Namespace(arg_dict={'1': '11', '2': '22', '3': '33', 'a': '', 'c': 'cc', '': 'dd'})

import argparse

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument(
    '--arg_dict',
    action=type(
        '', (argparse.Action, ),
        dict(__call__=lambda self, parser, namespace, values, option_string: getattr(
            namespace, self.dest).update(
                dict([
                    v.split('=') for v in values.replace(';', ',').split(',')
                    if len(v.split('=')) == 2
                ])))),
    default={},
    metavar='KEY1=VAL1,KEY2=VAL2;KEY3=VAL3...',
)
print(arg_parser.parse_args())
Plagon
  • 2,689
  • 1
  • 11
  • 23
miszel
  • 31
  • 1
0

A straight forward way of parsing an input like:

program.py --dict d --key key1 --value val1 --key key2 --value val2

is:

parser=argparse.ArgumentParser()
parser.add_argument('--dict')
parser.add_argument('--key', action='append')
parser.add_argument('--value', action='append')
args = parser.parse_args()

which should produce (if my mental parser is correct)

args = Namespace(dict='d', key=['key1','key2'], value=['value1','value2'])

You should be able construct a dictionary from that with:

adict = {k:v for k, v in zip(args.key, args.value)}

Using args.dict to assign this to a variable with that name requires some un-python trickery. The best would be to create a element in another dictionary with this name.

another_dict = {args.dict: adict}

This solution doesn't perform much error checking. For example, it doesn't make sure that there are the same number of keys and values. It also wouldn't let you create multiple dictionaries (i.e. repeated --dict arguments). It doesn't require any special order. --dict could occur after a --key key1 pair. Several --value arguments could be together.

Tying the key=value together as chepner does gets around a number of those problems.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
0

There is a simple solution in Python 3.6 if you're simply trying to convert argparse input to a dictionary. An example is as follows:

import argparse 

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', help='the path to the input file')
parser.add_argument('-o', '--output', help='the path to the output file')
args = parser.parse_args()

arguments = dict(args._get_kwargs())

for k, v in arguments.items():
    print(k, v) 

Given command line input such as python3 script_name.py --input 'input.txt' --output 'output.txt' the code would output to terminal:

input input.txt
output output.txt
Brian
  • 9
  • 1
  • 2
    No, I don't want to convert agruments into dictionary. It can be easily done without hack with hidden field: `arguments = vars(parser.parse_args())`. Please reread question with example. – orion_tvv Jul 09 '19 at 12:30
  • `arguments = vars(parser.parse_args())` that is soooo often what you want in this situation. – kontur Apr 21 '20 at 08:37
-1

As for the current libraries like argparse, docopt and click, none of them support using dict args. The best solution I can think of is to make a custom argparse.Action to support it youself:

import argparse

class MyAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        super(MyAction, self).__init__(option_strings, dest, nargs, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        print '%r %r %r' % (namespace, values, option_string)
        value_dict = {}
        values.reverse()
        while len(values) > 0:
            v = eval(values.pop()).lstrip('--') # This is like crazy hack, I know.
            k = eval(values.pop())
            value_dict[k] = v

        setattr(namespace, self.dest, value_dict)

parser = argparse.ArgumentParser()
parser.add_argument('-d', action=MyAction, nargs='*')
args = parser.parse_args('-d "--key" "key1" "--value" "val1" "--key" "key2" "--value" "val2"'.split())

print(args)
ljk321
  • 16,242
  • 7
  • 48
  • 60