Reputation: 1108
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)
Upvotes: 24
Views: 11564
Reputation: 231395
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))
Upvotes: 26
Reputation: 5092
You can see a similar question asked here.
Edit: Looking for a way that would not use an internal method, I found this discussion which suggested using vars()
. This works quite well:
import argparse
def main(arg1, arg2):
print arg1, arg2
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('arg1')
parser.add_argument('arg2')
args = parser.parse_args()
main(**vars(args))
Upvotes: 8
Reputation: 7519
What is wrong with doing
if __name__ == '__main__':
# do argparse stuff as above
main(args)
That is, why are you so hung up about giving main()
positional arguments?
To be honest, I usually do the argument parsing either a) [for small scripts etc.] at the beginning of the module, which provides me with a variable that's in the scope of all functions or b) [usually] inside main()
if I use your idiom:
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('arg1')
parser.add_argument('arg2')
args = parser.parse_args()
return args
def main():
args = parse_arguments()
# do stuff with args.arg1 and args.arg2
Upvotes: 5