Reputation: 169
Is there a proper, or at least better, way to get which command-line argument was used to set an Namespace argument (attribute) value?
I am currently using something like this:
>>> import argparse
>>>
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--do-a', '-a',
... default=False, action='store_true',
... dest='process_foo',
... help="Do some awesome a to the thing.")
>>> args = parser.parse_args()
>>>
>>> def get_argument(parser, dest):
... for action in parser._actions:
... if action.dest == dest:
... return action.option_strings[0], action.help
... return dest, ''
...
>>> get_argument(parser, 'process_foo')
('--do-a', 'Do some awesome a to the thing.')
This will probably work in 99% of cases; however, if more than one command-line argument can set process_foo
, this wont work, and accessing a 'hidden' instance attribute (parser._actions
) is kludgy at best. Is there a better way to do this?
I'm adding this to a module that all data science processes inherit which logs environment and other things so that we have better reproducibility. The module in question already auto-logs settings, parameters, command-line arguments, etc. but is not very user friendly in some aspects.
Upvotes: 2
Views: 2158
Reputation: 231395
Don't worry about the "hiddenness" of _actions
. That is the primary list where references to all Actions
created by add_argument
are stored. You shouldn't fiddle with the list, but you certainly can use it to collect information.
add_argument
creates an Action
object, puts it in _actions
(via the _add_action
method), and also returns it. If you don't like using _actions
you can collect your own list of references, using the object returned by add_argument
.
I see from _add_action
that it also puts flagged actions in a self._option_string_actions
dict, making it easier to pair an option string with its action
.
Parsing does not make any changes to the parser
, its attributes or the actions
. While it has various local variables (in the _parse_known_args
method), the only thing that is changed is the args
Namespace.
It keeps the access to args
as generic as possible, with getattr
, setattr
and hasattr
. This includes setting the defaults at the start of parsing. The parser does not maintain a record of which option-string triggered a particular take_action
and subsequent setattr
. However the __call__
of an Action
does get the string. For the most common 'store_action' the call is
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
I think all defined Action
subclasses use the self.dest
, but user defined ones don't have to. They can even set other namespace attributes, or none (e.g. help doesn't set anything). They could also record the option_string
.
It is also possible to set a namespace attribute without going through the defined Actions. Your dest
test won't help with these.
https://docs.python.org/3/library/argparse.html#parser-defaults
shows how attributes can be set without defining them in an argument. Subcommands shows how this can be used to define a function that will be used with a particular parser.
https://docs.python.org/3/library/argparse.html#the-namespace-object also shows that it's possible to supply a partially initialize namespace.
Upvotes: 1
Reputation: 804
I would suggest creating your own action class derived from argarse.Action that will not only store the parsed value in the namespace, but also store the parsed value's option string in the namespace.
Full working example:
import argparse
class StoreTrueWithOptionStringAction(argparse.Action):
def __init__(self,
option_strings,
dest,
default=None,
required=False,
help=None,
metavar=None):
super().__init__(option_strings=option_strings,
dest=dest,
nargs=0,
const=True,
default=default,
required=required,
help=help)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const)
if option_string is not None:
setattr(namespace, f'{self.dest}_option_string', option_string)
def get_parser():
parser = argparse.ArgumentParser()
parser.add_argument('--bar', action=StoreTrueWithOptionStringAction, dest='foo', default=False)
parser.add_argument('--baz', action=StoreTrueWithOptionStringAction, dest='foo', default=False)
return parser
def main():
parser = get_parser()
args = parser.parse_args()
print(args.foo)
print(args.foo_option_string)
if __name__ == '__main__':
main()
Output:
$ python3 main.py --bar
True
--bar
$ python3 main.py --baz
True
--baz
Upvotes: 2