How can I require my python script's argument to be a float between 0.0-1.0 using argparse?
Solution 1
The type
parameter to add_argument
just needs to be a callable object that takes a string and returns a converted value. You can write a wrapper around float
that checks its value and raises an error if it is out of range.
def restricted_float(x):
try:
x = float(x)
except ValueError:
raise argparse.ArgumentTypeError("%r not a floating-point literal" % (x,))
if x < 0.0 or x > 1.0:
raise argparse.ArgumentTypeError("%r not in range [0.0, 1.0]"%(x,))
return x
p = argparse.ArgumentParser()
p.add_argument("--arg", type=restricted_float)
Solution 2
Here is a method that uses the choices
parameter to add_argument
, with a custom class that is considered "equal" to any float within the specified range:
import argparse
class Range(object):
def __init__(self, start, end):
self.start = start
self.end = end
def __eq__(self, other):
return self.start <= other <= self.end
parser = argparse.ArgumentParser()
parser.add_argument('--foo', type=float, choices=[Range(0.0, 1.0)])
Solution 3
Adding str makes that the boundaries are visuable in the help.
import argparse
class Range(object):
def __init__(self, start, end):
self.start = start
self.end = end
def __eq__(self, other):
return self.start <= other <= self.end
def __contains__(self, item):
return self.__eq__(item)
def __iter__(self):
yield self
def __str__(self):
return '[{0},{1}]'.format(self.start, self.end)
parser = argparse.ArgumentParser()
parser.add_argument('--foo', type=float, choices=Range(0.0, 1.0))
parser.add_argument('--bar', type=float, choices=[Range(0.0, 1.0), Range(2.0,3.0)])
Solution 4
The argparse.add_argument call expects an iterable as 'choices' parameter. So what about adding the iterable property to the Range class above. So both scenarios could be used:
import argparse
class Range(object):
def __init__(self, start, end):
self.start = start
self.end = end
def __eq__(self, other):
return self.start <= other <= self.end
def __contains__(self, item):
return self.__eq__(item)
def __iter__(self):
yield self
parser = argparse.ArgumentParser()
parser.add_argument('--foo', type=float, choices=Range(0.0, 1.0))
parser.add_argument('--bar', type=float, choices=[Range(0.0, 1.0), Range(2.0,3.0)])
Dolan Antenucci
Updated on June 06, 2022Comments
-
Dolan Antenucci about 2 years
I'd like to use
argparse
on Python 2.7 to require that one of my script's parameters be between the range of 0.0 and 1.0. Doesargparse.add_argument()
support this? -
Dolan Antenucci almost 12 yearsI like this one because it leaves the exception raising to argparse. Thanks!
-
chepner almost 12 yearsOne suggestion: have your
Range
class implement the__contains__
method; then you can saychoices=Range(0.0, 1.0)
instead of wrapping it in a list. -
Dolan Antenucci almost 12 yearsI was originally going to choose FJ's as the accepted answer, simply because I like the "cleanness" of it (obviously arguable), but the simplicity of this won me over, and I'm using it in my code. Thanks!
-
Martin Thoma over 9 yearsNote:
restricted_float
should probably be replaced by whatever it represents as it will be shown in error messages. For example, I used this pattern in a toy projectgeocodertools
and called itlongitude
andlatitude
. -
RickardSjogren over 8 yearsThe
__contains__
-approach givesValueError: length of metavar tuple does not match nargs
using Python 3.4. Otherwise it works well and I implemented__repr__
to return'{0}-{1}'.format(self.start, self.end)
for prettier help text as well. -
James McCormac over 7 yearsAndrew's answer combined with @RickardSjogren comment gives a great result. Thanks!
-
PJ_Finnegan about 6 yearsEven with
__contains__
redefined, in Python 3.5.2 I get:TypeError: 'Range' object is not iterable
if I don't put it in a single-element list. Solved redefining:def __getitem__(self, index): if index == 0: return self else: raise IndexError()
-
rasen58 almost 5 yearsI tried using @PJ_Finnegan
__getitem__
addition, but it did not work in python 3.6.5. I still getValueError: length of metavar tuple does not match nargs
-
chepner over 4 years@Danijel What do you mean? There are a lot of different types you might want to define;
argparse
can't know about them all. This is howargparse
supports converting the string argument to whatever value you want. -
Danijel over 4 yearsI was expecting that
float
would be supported asint
is with regards torange
. Am I missing something? -
chepner over 4 years@Danijel A range of floats wouldn't be terribly useful. What's the next float after
0.5
?0.6
, or0.51
, or0.501
, or...? While technically discrete,float
isn't enumerable in any useful way. -
chepner over 4 yearsAn interval type that only supported inclusion, rather than enumeration, might be useful, but Python doesn't provide one.
-
Danijel over 4 years@chepner Thanks. I was thinking about a from-to range of floats, not individual values.
-
Chris over 3 yearsFor me the argparse error message seems to show the class's repr so I just changed your
__str__
method to__repr__
to get a nice error message.