C compilers have the -D
switch to define a preprocessor constant at compilation time. It is called like -D name=value
, or -D name
and value
defaults to 1.
Can a similar thing be done with python argparse? That is, can you create an option that meets these criteria?
Desired usage:
ap = argparse.ArgumentParser()
ap.add_argument("-D",
#### what goes here ? ####
default=1
)
ap.parse_args(["-D", "foo=bar", "-D", "baz"])
# ==> Namespace(D={'foo': 'bar', 'baz': 1})
If I just use nargs="append"
, I can not specify an automatic default in the add_argument call, and I additionally have to do the string processing myself on the resultant list, which is kind of hairy, and I would like to avoid that.
There is nothing in argparse
directly for updating mapping types, currently. I've seen people use a normal option and then put type=json.loads
as a hackaround, but it's not very likable.
I think a custom Action type meets the requirements in the question:
import argparse
class MappingAction(argparse.Action):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._default = self.default
self.default = {}
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest) or {}
key, sep, val = values.partition("=")
if not sep:
val = self._default
items[key] = val
setattr(namespace, self.dest, items)
Using the default=1
to indicate the default value of unspecified mapping values is a bit off, so I'd encourage you to rethink that API. In the context of an add_argument
call, using default=1
would suggest you want a default value of 1
in the namespace if no -D
were specified. Whereas I think you'd probably prefer an empty dict as the default value in that case.
If the default doesn't need to be configurable, I'd probably recommend to remove the "indirection" used in this action and just hardcode the "1" instead of using self._default
. This would simplify your action to this:
class MappingAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest) or {}
key, sep, val = values.partition("=")
if not sep:
val = "1"
items[key] = val
setattr(namespace, self.dest, items)
Usage demo:
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-D", action=MappingAction)
args = ap.parse_args(['-Dfoo=bar', '-Dbaz', '-D', 'asd=qwe', '-D', 'hi=1'])
print(args) # Namespace(D={'foo': 'bar', 'baz': '1', 'asd': 'qwe', 'hi': '1'})