I'd like to build a parser.add_argument(...)
to map given argument with constant defined in my code.
Suppose I have the following
import argparse
# Both are the same type
CONST_A = <something>
CONST_B = <otherthing>
parser = argparse.ArgumentParser()
parser.add_argument(...)
# I'd like the following to be true:
parser.parse_args("--foo A".split()).foo == CONST_A
parser.parse_args("--foo B".split()).foo == CONST_B
What can I put in lieu of ...
?
The best I could do with const
was:
import argparse
# Both are the same type
CONST_A = 10
CONST_B = 20
parser = argparse.ArgumentParser()
status_group = parser.add_mutually_exclusive_group(required=True)
status_group.add_argument("-a", const=CONST_A, action='store_const')
status_group.add_argument("-b", const=CONST_B, action='store_const')
# I'd like the following to be true:
print parser.parse_args("-a".split()).a == CONST_A # True
print parser.parse_args("-b".split()).b == CONST_B # True
Note that the constants are saved into tw odifferent attributes a
and b
, witch suits me not :(
The simplest way is to take advantage of the type=
option in add_argument
like @hpaulj did although it can be generalized with a factory function:
def argconv(**convs):
def parse_argument(arg):
if arg in convs:
return convs[arg]
else:
msg = "invalid choice: {!r} (choose from {})"
choices = ", ".join(sorted(repr(choice) for choice in convs.keys()))
raise argparse.ArgumentTypeError(msg.format(arg,choices))
return parse_argument
then in lieu of ...
just use type=argconv(A=CONST_A, B=CONST_B)
:
parser.add_argument("--foo", type=argconv(A=CONST_A, B=CONST_B))
And then everything will work as you want it to in your example.
The following is the first answer I posted, it is still valid but isn't nearly as simple as the above solution.
An alternate method is to make a class that inherits from argparse.ArgumentParser
and override parse_args
to modify the result as it is generated:
import argparse
class MappedParser(argparse.ArgumentParser):
mapping = {} #backup if you don't use def_mapping
def def_mapping(self,**options):
self.mapping = options
def parse_args(self,args=None,namespace=None):
result = argparse.ArgumentParser.parse_args(self,args,namespace)
for name,options in self.mapping.items(): #by default this is is empty so the loop is skipped
if name in result:
key = getattr(result,name)
if key in options:
replace_with = options[key]
setattr(result,name,replace_with)
else:
self.error("option {name!r} got invalid value: {key!r}\n must be one of {valid}".format(name=name,key=key,valid=tuple(options.keys())))
return #error should exit program but I'll leave this just to be safe.
return result
this way the rest of your (example) program would look like this:
# There is nothing restricting their type.
CONST_A = "<something>"
CONST_B = ["other value", "type is irrelevent"]
parser = MappedParser() #constructor is same
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B})
parser.add_argument("--foo") # and this is unchanged
# the following is now true:
print(parser.parse_args("--foo A".split()).foo is CONST_A)
print(parser.parse_args("--foo B".split()).foo is CONST_B)
#note that 'is' operator works so it is even the same reference
#this gives decent error message
parser.parse_args("--foo INVALID".split())
print("when parser.error() is called the program ends so this never is printed")
Add extra options like this:
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B,"C":"third option"})
or extra arguments like this:
parser.def_mapping(foo={"A":CONST_A, "B":CONST_B},
conv={"int":int,"float":float})
as well any added arguments that are not specified in def_mapping
are left alone so it is very easy to implement.
This is an interesting question. To the best of my knowledge, argparse
does not support this directly.
If you find this pattern occurs often, you can write a small utility class that does this for you, by transforming args
into a dictionary via vars
:
class Switcher(object):
def __init__(self, d):
self._d = d
def __call__(self, args):
args_ = vars(args)
for k, v in self._d.items():
if args_[k]:
return v
You can use it as follows. Say your parser is defined by:
import argparse
parser = argparse.ArgumentParser()
g = parser.add_mutually_exclusive_group()
g.add_argument('-a', action='store_true', default=False)
g.add_argument('-b', action='store_true', default=False)
Then you can define a Switcher
via:
s = Switcher({'a': 10, 'b': 20})
and use it like so:
>>> print s(parser.parse_args(['-a']))
10
>>> print s(parser.parse_args(['-b']))
20
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