I am using argparse's action to add various data to a class. I would like to use that action on the default value if that arg is not provided at the command line. Is this possible? Thanks!
-
possible duplicate of [Python argparse: default value or specified value](http://stackoverflow.com/questions/15301147/python-argparse-default-value-or-specified-value) – Feb 05 '14 at 17:07
-
@LutzHorn, this is not quite what I need (I don't think) because I want to 1: store argname=val and 2: apply an arbitrary argparse.Action as if --argname=val was set at the command line – Max Bileschi Feb 05 '14 at 17:24
2 Answers
argparse
does not use the action
when applying the default
. It just uses setattr
. It may use the type
if the default is a string. But you can invoke the action
directly.
Here I use a custom action class borrowed from the documentation. In the first parse_args
nothing happens. Then I create a new namespace
, and invoke the action on the default. Then I pass that namespace to parse_args
. To understand this, you many need to import it into an interactive shell, and examine the attributes of the namespace and action.
# sample custom action from docs
class FooAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
print('Setting: %r %r %r' % (namespace, values, option_string))
setattr(namespace, self.dest, 'action:'+values)
p = argparse.ArgumentParser()
a1 = p.add_argument('--foo', action=FooAction, default='default')
print 'action:',a1
print p.parse_args([])
ns = argparse.Namespace()
a1(p, ns, a1.default, 'no string') # call action
print p.parse_args([],ns)
print p.parse_args(['--foo','1'],ns)
which produces:
action: FooAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default='default', type=None, choices=None, help=None, metavar=None)
Namespace(foo='default')
Setting: Namespace() 'default' 'no string'
Namespace(foo='action:default')
Setting: Namespace(foo='action:default') '1' '--foo'
Namespace(foo='action:1')
I tailored the output to highlight when the action is being used.
Here's a way of performing a special action on an argument that isn't given on the command line (or given with a value == to the default). It's a simplification of the class given in https://stackoverflow.com/a/24638908/901925.
class Parser1:
def __init__(self, desc):
self.parser = argparse.ArgumentParser(description=desc)
self.actions = []
def milestone(self, help_='milestone for latest release.', default=None):
action = self.parser.add_argument('-m', '--milestone', help=help_, default=default)
self.actions.append(action)
return self
def parse(self):
args = self.parser.parse_args()
for a in self.actions:
if getattr(args, a.dest) == a.default:
print 'Please specify', a.dest
values = raw_input('>')
setattr(args, a.dest, values)
return args
print Parser1('desc').milestone(default='PROMPT').parse()
The prompting is done after parse_args
. I don't see any reason to call parse_args
again.
-
Thank you. This is surely one way of doing it. (Also, it provides good examples for other parts of using argparse). I ended up pulling the body of FooAction.__call__ to another method, and manually iterating over arguments like your a1. It's really a shame it's not built in. – Max Bileschi Feb 05 '14 at 22:40
-
+1 - I used it to prompt the user when no option is passed in - does it really have to be so hacky ? No way via the API to require an action to run if an option is not set ? – Mr_and_Mrs_D Jul 08 '14 at 01:33
-
I would wait until the whole command line is parsed before asking the user for further input. The simplest way to do that is run `args=parse_args...`, and then base my action on the contents of `args. No point trying to do something 'the first time I don't encounter an option'! – hpaulj Jul 08 '14 at 16:18
-
@hpaulj: well if no option is specified or no option is default - it certainly should be possible. Include `@user_to_notify` for me to be notified - saw this by chance. Will post my code so you see what I mean – Mr_and_Mrs_D Jul 08 '14 at 18:03
-
2The key point of your solution is that ` p.add_argument` returns the action - why is it not documented : https://docs.python.org/2.7/library/argparse.html#the-add-argument-method ? – Mr_and_Mrs_D Jul 08 '14 at 18:23
-
I agree it should. There isn't even an example demonstrating it, though it you create a parser in an interactive shell, it is obvious that (nearly) all of the `add..` methods return some sort of object - a parser, a group, or an action. And in typical Python fashion, it is possible to examine or even modify the attributes of those objects. – hpaulj Jul 08 '14 at 20:42
-
Thank you very much for your insights! Would be great if there was a way to specify an action to run if user did not give an option (default existing or not) but hey, at least one can workaround. One last question. Do I need to say `default='foo'` (`default='PROMPT'` in your example) or leaving it None will do ? – Mr_and_Mrs_D Jul 11 '14 at 01:37
-
`None` is a good default. Besides being the default `default`, is easy test `is None`, and won't be mistaken for any user given value. – hpaulj Jul 11 '14 at 03:20
-
In http://bugs.python.org/issue11588 I explore a means of letting the user define tests that would be run shortly before the end of `parse_args` (using a list of 'seen-actions'). I can imagine defining your user-action method to work at that point. – hpaulj Jul 11 '14 at 03:26
-
Thanks: If you remember it please post a comment here if/when that issue is closed :) – Mr_and_Mrs_D Jul 12 '14 at 15:24
I needed to prompt the user if an option was not specified - that's how I did it:
class _PromptUserAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if values == self.default:
print 'Please specify', self.dest
values = raw_input('>')
setattr(namespace, self.dest, values)
class Parser:
def __init__(self, desc, add_h=True):
self.parser = argparse.ArgumentParser(description=desc, add_help=add_h,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
#actions to be run on options if not specified (using default to check)
self.actions = []
@staticmethod
def new(description, add_help=True):
return Parser(description, add_help)
# ...
def milestone(self, help_='Specify the milestone for latest release.'):
action = self.parser.add_argument('-m', '--milestone',
dest='milestone',
action=_PromptUserAction,
default='PROMPT', # needed I think
type=str,
help=help_)
self.actions.append(action)
return self
def parse(self):
"""
Return an object which can be used to get the arguments as in:
parser_instance.parse().milestone
:return: ArgumentParser
"""
args = self.parser.parse_args()
# see: http://stackoverflow.com/a/21588198/281545
dic = vars(args)
ns = argparse.Namespace()
for a in self.actions:
if dic[a.dest] == a.default:
a(self.parser, ns, a.default) # call action
# duh - can I avoid it ?
import sys
return self.parser.parse_args(sys.argv[1:],ns)
I am interested if this can somehow be done without having to reparse the args (the import sys
part). Maybe some constructor options for argparse.Action
?

- 32,208
- 39
- 178
- 361
-
I've added a simplified version of your example to my answer. Is that the functionality that you are seeking? – hpaulj Jul 08 '14 at 23:13
-
@hpaulj: how pythonic - thanks - I insisted on trying to "run my action" - will use it at some point and post back if I come across any weirdness – Mr_and_Mrs_D Jul 09 '14 at 00:24