Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unpacking arguments from argparse

I've been writing some command line python programs and using argparse to do it. I've been structuring my code somewhat as follows.

def main(arg1, arg2):
    # magic
    pass

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('arg1')
    parser.add_argument('arg2')

    args = parser.parse_args()

    main(args.arg1, args.arg2)

It's really super irritating to have to call out arg1 and arg2 3 times. I understand having to do it twice.

Is there some way to treat the namespace returned by the parse_args function as a tuple? Or better yet as a tuple and a dict for optional args and do unpacking?

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('arg1')
    parser.add_argument('arg2')
    parser.add_argument('--opt-arg', default='default_value')

    args, kwargs = parser.magic_method_call_that_would_make_my_life_amazing()

    # I get goosebumps just thinking about this
    main(*args, **kwargs)
like image 210
Ben Hoff Avatar asked Mar 06 '16 02:03

Ben Hoff


People also ask

How do you unpack an argument in Python?

Unpacking keyword arguments When the arguments are in the form of a dictionary, we can unpack them during the function call using the ** operator. A double asterisk ** is used for unpacking a dictionary and passing it as keyword arguments during the function call.

How do you pass arguments to Argparse?

First, we need the argparse package, so we go ahead and import it on Line 2. On Line 5 we instantiate the ArgumentParser object as ap . Then on Lines 6 and 7 we add our only argument, --name . We must specify both shorthand ( -n ) and longhand versions ( --name ) where either flag could be used in the command line.

What is the use of Argparse in Python?

The argparse module is a powerful part of the Python standard library that allows you to write command-line interfaces for your code. This tutorial introduced you to the foundations of argparse : you wrote a command-line interface that accepted positional and optional arguments, and exposed help text to the user.

What is dest in Argparse?

The dest attribute of a positional argument equals the first argument given to the add_argument() function. An optional argument's dest attribute equals the first long option string without -- . Any subsequent - in the long option string is converted to _ .


1 Answers

https://docs.python.org/3/library/argparse.html#the-namespace-object

This class is deliberately simple, just an object subclass with a readable string representation. If you prefer to have dict-like view of the attributes, you can use the standard Python idiom, vars():

>>>
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo')
>>> args = parser.parse_args(['--foo', 'BAR'])
>>> vars(args)
{'foo': 'BAR'}

Note that one of the big advances, or changes at least, from optparse to argparse is that positional arguments, such as yours, are treated the same as optionals. They both appear in the args Namespace object. In optparse, positionals are just the left overs from parsing defined options. You could get the same effect in argparse by omiting your arguments and using parse_known_args:

parser = argparse.ArgumentParser()
args, extras = parser.parse_known_args()

args is now a Namespace, and extras a list. You could then call your function as:

myfoo(*extras, **vars(args))

For example:

In [994]: import argparse
In [995]: def foo(*args, **kwargs):
   .....:     print(args)
   .....:     print(kwargs)
   .....:     
In [996]: parser=argparse.ArgumentParser()
In [997]: parser.add_argument('-f','--foo')
Out[997]: _StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [998]: args,extras = parser.parse_known_args(['-f','foobar','arg1','arg2'])
In [999]: args
Out[999]: Namespace(foo='foobar')
In [1000]: extras
Out[1000]: ['arg1', 'arg2']
In [1001]: foo(*extras, **vars(args))
('arg1', 'arg2')
{'foo': 'foobar'}

That same argparse paragraph shows that you can define your own Namespace class. It wouldn't be hard to define one that behaves like a dictionary (for use as **args) and as namespace. All argparse requires is that it works with getattr and setattr.

In [1002]: getattr(args,'foo')
Out[1002]: 'foobar'
In [1004]: setattr(args,'bar','ugg')
In [1005]: args
Out[1005]: Namespace(bar='ugg', foo='foobar')

another standard Python feature lets me pass vars(args) as a tuple:

In [1013]: foo(*vars(args).items())
(('foo', 'foobar'), ('bar', 'ugg'))
{}

For a similar answer from last January: https://stackoverflow.com/a/34932478/901925

Neatly pass positional arguments as args and optional arguments as kwargs from argpase to a function

There I give ideas on how to separate out 'positionals' from 'optionals' after parsing.


Here's a custom namespace class that includes, in its API, a means of returning itself as a dictionary:

In [1014]: class MyNameSpace(argparse.Namespace):
   ......:     def asdict(self):
   ......:         return vars(self)
   ......:     
In [1015]: args = parser.parse_args(['-f','foobar'], namespace=MyNameSpace())
In [1016]: args
Out[1016]: MyNameSpace(foo='foobar')
In [1017]: foo(**args.asdict())
()
{'foo': 'foobar'}

Another idea - use one of the multiple nargs (2,'*','+') for the positional argument. Then you have only one name to type when passing it to your function.

parser.add_argument('pos',nargs='+')
args = ...
args.pos # a list, possibly empty
foo(*args.pos, **vars(args))
like image 84
hpaulj Avatar answered Oct 16 '22 15:10

hpaulj