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 valueuser1907906– user19079062014-02-05 17:07:12 +00:00Commented Feb 5, 2014 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 lineMax Bileschi– Max Bileschi2014-02-05 17:24:34 +00:00Commented Feb 5, 2014 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.
10 Comments
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'!@user_to_notify for me to be notified - saw this by chance. Will post my code so you see what I meanI 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 ?