32

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,400
  • 4
  • 17
  • 26

9 Answers9

40

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
  • 348
  • 4
  • 13
storm_m2138
  • 2,075
  • 2
  • 18
  • 17
19

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
  • 446,329
  • 63
  • 468
  • 610
11

just another easy way:

parser = argparse.ArgumentParser()
parser.add_argument('--key1')
parser.add_argument('--key2')
args = parser.parse_args()
my_dict = args.__dict__
Clemens69
  • 127
  • 1
  • 3
  • 3
    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
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,256
  • 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
  • 1,580
  • 1
  • 10
  • 19
miszel
  • 31
  • 1
3

use vars

d = vars(parser.parse_args())
范晔斌
  • 31
  • 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
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
  • 201,845
  • 13
  • 203
  • 313
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)
skyline75489
  • 15,342
  • 7
  • 46
  • 60