I’ve used python’s argparse
module in the past to get command line options for scripts. It provides built-in help, which is nice, and support for both positional and optional arguments. The inelegant way I’ve used it in the past, though, involves a lot of if
statements to test for the various arguments and call to call the right function. I don’t like that implementation, and reading through the docs and tutorials never really helped.
Today, I was playing around with argparse
again, trying to cleanup and simplify some code and stumbled across what seems to me to be a better way.
The reason I used all those if
statements in the past is because the parser returns a Namespace
which isn’t iterable:
import argparse p = argparse.ArgumentParser(description="parse some things.") p.add_argument("cmd", help=argparse.SUPPRESS, nargs="*") p.add_argument("-d","--date") p.add_argument("-p","--project") p.add_argument("-c","--context") p.add_argument("-l","--list") opts = p.parse_args() print opts
Run this code with input like this:
add "A task to be done." -d=today -p=myproject -l=mylist
(obviously, I’m dealing with task management in the real script) and the parser will return a Namespace object:
Namespace(cmd=['add', 'A task to be done.'], context=None, date='today', list='mylist', project='myproject')
Type Namespace isn’t iterable, and I want a convenient way to get rid of arguments that returned None
. Finally stumbled across the answer today– vars
. We can extract from the Namespace object to a dictionary by passing the object to vars
:
opts = vars(p.parse_args())
Why bother? Well, now we have a dictionary that can more elegantly be dealt with, and we don’t have to hard code all possible keyword arguments for the script. opts
is now:
{'date': 'today', 'project': 'myproject', 'cmd': ['add', 'A task to be done.'], 'list': 'mylist', 'context': None}
Now, in two quick steps we can pop the positional argument (cmd), and cull the unused optional arguments:
command = opts.pop('cmd') options = { k : opts[k] for k in opts if opts[k] != None }
That gives us a list of the positional argument and its associated text, and a dictionary of the utilized optional arguments. Beginning with Python 2.7, dictionaries get comprehensions much as lists had for a long time. The second line of that couplet uses a dictionary comprehension to get the key:value pairs from opts that aren’t empty. This is nice, because we can call the correct function now using the list command
, along with passing it arguments from both command
and options
.
command[0]
gives us a string that names the function we need. So, how can we then call that function (without using eval
/exec
)? It’s easy with a dictionary that points to the functions we’ve defined. By way of example:
def add(task, *args): print task for arg in args: print arg functions = { 'add':add }
The add function here obviously doesn’t really add a task. Just for show. Nonetheless, we can call it with our command/options variables thusly:
functions[command[0]](command[1],options)
I like this, because it makes parsing a wide range of potential positional arguments without having to add each one of them to the ArgumentParser. That does, however, cause problems for help output. We won’t have a help script for every possible command this way. My solution for this is to move the help cues into the program description, and exclude the “cmd” argument from the help output:
p = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,description=''' Parse tasks. Options include: add: add a task, followed by the task list: list the tasks. Filter with list, project, or context options del: delete task by number clear: clear tasks ''') p.add_argument("cmd", help=argparse.SUPPRESS, nargs="*")
That’s it for now. And really, most of this is just so I’ll remember it.
[…] I mentioned in my last post, I’ve been re-working a script to add tasks to a task manager. I’m using The Hit List […]
Hi!
I just skimmed over your blog entry and noticed that you “want a convenient way to get rid of arguments that returned None”. The argparse module docs mention that “Providing default=argparse.SUPPRESS causes no attribute to be added if the command-line argument was not present”.. Best regards, Gerald Hofmair
Gerald–
Thanks! I missed that when looking around the docs and google.
Thanks! Didn’t know about vars(). This really helped me.
[…] https://parezcoydigo.wordpress.com/2012/08/04/from-argparse-to-dictionary-in-python-2-7/ […]
Thanks. I was looking exactly for this.
Useful! Thanks.
How about just using ap.parse_args().__dict__() ?
Super elegant Elias. :P Although, that is exactly what vars does. I’ll never understand why they didn’t just return a dict. The namespace object is idiotic, especially since there are a bunch of hidden reserved words that can’t be used an attributes in python. Happy hunting for them!
Also works great if you want to pass the argparse namespace to a class defined with the attrs package. Thanks.