Accepting a dictionary as an argument with argparse and python

user2745896 picture user2745896 · Sep 4, 2013 · Viewed 44.4k times · Source

I'm trying to accept an argument of type=dict with argparse but no matter the input it gives an error of invalid dict value.

#!/usr/bin/env python

import argparse

MYDICT = {'key': 'value'}

parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mydict", action="store",
                    required=False, type=dict,
                    default=MYDICT)

args = parser.parse_args()

print args.mydict

This is what happens when I try and pass a dictionary to the script

./argp.py -m "{'key1': 'value1'}"
usage: argp.py [-h] [-m MYDICT]
argp.py: error: argument -m/--mydict: invalid dict value: "{'key1': 'value1'}"

Looking at the documents I would think that this would be possible.

http://docs.python.org/dev/library/argparse.html

“Any object that supports the in operator can be passed as the choices value, so dict objects, set objects, custom containers, etc. are all supported.”

Answer

Michael Aquilina picture Michael Aquilina · Sep 4, 2013

I do not think it is possible to pass a dictionary as an argument in the command line because there doesn't exist a conversion function from string to dict (EDIT: A hack is possible, see below). What you are essentially telling python to do is:

dict("{'key1': 'value1'}")

Which if you try it out in the python console, does not work.

What the phrase:

"Any object that supports the in operator can be passed as the choices value, so dict objects, set objects, custom containers, etc. are all supported."

refers to is the choices argument that can be passed with the add_argument function - not to the type argument.

Your best bet is to probably accept your argument as a string and then convert it using the json capabilities of python:

parser.add_argument('-m', '--my-dict', type=str)
args = parser.parse_args()

import json
my_dictionary = json.loads(args.my_dict)

You can then pass a dictionary in the form of a string. You can try the json encoder/decoder out for yourself in the python console to see how it works:

>>>json.loads('{"value1":"key1"}')
{u'value1': u'key1'}

EDIT: hpaulj has pointed out to me that you can "hack" the type parameter by passing it json.loads.

import json
parser.add_argument('-d', '--my-dict', type=json.loads)
args = parse.parse_args()

mydict = args.my_dict  # Will return a dictionary

NOTE: The input format you pass is not the same as python dictionary but is probably similar enough for your use case.

The reason this works is actually quite interesting because internally argparse will just use the parameter value as a function to convert the argument. i.e. if type=int then it will use int(arg) or if type=json.loads then json.loads(arg)

This also means that you can pass any function which takes a single parameter in as the argument to type and perform custom conversions if you need to :)