This is slightly related to the topic covered in a question about allowing an argument to be specified multiple times.
I'd like to be able to specify an option multiple times like this:
tool --foo 1 --foo 2 --foo 3
And also like this:
tool a b c
I'd also like to support both at the same time:
tool a b c --foo 1 --foo2 --foo 3
This works fine with:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('foo', nargs='*', action='append')
parser.add_argument('--foo', nargs='*', dest='foo', action='append')
The result list can be easily flattened out:
args = parser.parse_args('a b c --foo 1 --foo 2 --foo 3'.split())
args.foo = [el for elements in args.foo for el in elements]
yields:
>>> args
Namespace(foo=['a', 'b', 'c', '1', '2', '3'])
How do I add a default value in a way that the default is not being used as soon as one argument is specified by the user?
If adding just default=[['spam']]
to one of the add_argument()
calls, the default is always part of the result. I cannot get argparse to remove it by itself as soon as a user provides an argument herself.
I'm hoping that there's a solution with what argparse
already provides itself.
I think this is a slightly more clean variation on the other answer (relying on the self.default
attribute of custom actions):
import argparse
import sys
class Extender(argparse.Action):
def __call__(self,parser,namespace,values,option_strings=None):
#Need None here incase `argparse.SUPPRESS` was supplied for `dest`
dest = getattr(namespace,self.dest,None)
#print dest,self.default,values,option_strings
if(not hasattr(dest,'extend') or dest == self.default):
dest = []
setattr(namespace,self.dest,dest)
#if default isn't set to None, this method might be called
# with the default as `values` for other arguements which
# share this destination.
parser.set_defaults(**{self.dest:None})
try:
dest.extend(values)
except ValueError:
dest.append(values)
#another option:
#if not isinstance(values,basestring):
# dest.extend(values)
#else:
# dest.append(values) #It's a string. Oops.
def new_parser(args):
parser = argparse.ArgumentParser()
parser.add_argument('foo', nargs='*',action=Extender)
parser.add_argument('--foo', nargs='*', dest='foo', action=Extender)
parser.set_defaults(foo = [['spam']])
return parser.parse_args(args.split())
tests = {'a b c --foo 1 --foo 2 --foo 3':['a','b','c','1','2','3'],
'':[['spam']],
'a b c --foo 1 2 3':['a','b','c','1','2','3'],
'--foo 1':['1'],
'a':['a']}
for s,r in tests.items():
print ( "parsing: {0}".format(s) )
args = new_parser(s)
if(args.foo != r):
print ("ERROR")
print (args.foo)
print (r)
sys.exit(1)
print ( args )
print ('*'*80)
Also note that I've used parser.set_defaults(...)
to set the default for the foo
attribute.
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