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)
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.
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.
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.
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 _ .
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With