Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify a minimum or maximum float value with argparse

How can I specify a minimum or maximum floating point argument using argprase? I'd like to be able to provide a command-line argument between a min and max floating point value.

The closest thing I can find is the choices option in add_argument(), but that only specifies allowable values for the argument.

parser.add_argument("L", type=float, choices=range(2))

The command-line argument 0.5 for L fails:

invalid choice: 0.5 (choose from 0, 1)
like image 584
EarthIsHome Avatar asked Mar 24 '19 13:03

EarthIsHome


People also ask

Does Max work on float?

Here is a list of floating-point numbers we can call the max() function on. Out of all the floating-point values, max() finds 105.8 as the largest.

How do you add an optional argument in Argparse?

To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

What is Argparse ArgumentParser ()?

The argparse module provides a convenient interface to handle command-line arguments. It displays the generic usage of the program, help, and errors. The parse_args() function of the ArgumentParser class parses arguments and adds value as an attribute dest of the object.


4 Answers

You can (and should) use a custom type function. It's much more user friendly.

def range_limited_float_type(arg):
    """ Type function for argparse - a float within some predefined bounds """
    try:
        f = float(arg)
    except ValueError:    
        raise argparse.ArgumentTypeError("Must be a floating point number")
    if f < MIN_VAL or f > MAX_VAL:
        raise argparse.ArgumentTypeError("Argument must be < " + str(MAX_VAL) + "and > " + str(MIN_VAL))
    return f



parser.add_argument(
    '-f',
    '--float',
    type=range_limited_float_type,
    help='Your argument description'
)
like image 161
rdas Avatar answered Nov 04 '22 11:11

rdas


Solution

Based on the nice solution from @rdas I created a new solution that allows dynamic specification of a float range that should be checked by ArgumentParser.

Code

def float_range(mini,maxi):
    """Return function handle of an argument type function for 
       ArgumentParser checking a float range: mini <= arg <= maxi
         mini - minimum acceptable argument
         maxi - maximum acceptable argument"""

    # Define the function with default arguments
    def float_range_checker(arg):
        """New Type function for argparse - a float within predefined range."""

        try:
            f = float(arg)
        except ValueError:    
            raise argparse.ArgumentTypeError("must be a floating point number")
        if f < mini or f > maxi:
            raise argparse.ArgumentTypeError("must be in range [" + str(mini) + " .. " + str(maxi)+"]")
        return f

    # Return function handle to checking function
    return float_range_checker

How to use

You can use this function as a dynamic argument type generator in ArgumentParser:

parser = ArgumentParser(description='%(prog)s: My programm')
parser.add_argument('--Temperature', type=float_range(8,25), 
                        help='Set target temperature between [8°C .. 25°C].')

How it works

The function float_range(mini,maxi) creates a local context where mini and maxi are known variables. Inside this context function float_range_checker() is defined and a handle to it returned. When ArgumentParser calls this function handle, the context with the values provided at calling float_range() is restored and the range check can take place.

like image 31
Georg W. Avatar answered Nov 04 '22 11:11

Georg W.


After playing around with this, the simplest answer is to handle the command-line input validation of minimum or maximum values outside of argprase.

This leaves writing a function and conditional to check the float value. Exit the program if the conditional is met:

import sys

# function to print error to stderr
def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs

# input validation
if args.L <=0:
    eprint("Input error. Length is less than 0. Please enter a positive length. Exiting.")
    sys.exit(1)
like image 45
EarthIsHome Avatar answered Nov 04 '22 10:11

EarthIsHome


Inspired from the already given answers, here's a more general solution that extends to all types that can be created from a string input and with working '<' and '>' operators:

def ranged_type(value_type, min_value, max_value):
    """
    Return function handle of an argument type function for ArgumentParser checking a range:
        min_value <= arg <= max_value

    Parameters
    ----------
    value_type  - value-type to convert arg to
    min_value   - minimum acceptable argument
    max_value   - maximum acceptable argument

    Returns
    -------
    function handle of an argument type function for ArgumentParser


    Usage
    -----
        ranged_type(float, 0.0, 1.0)

    """

    def range_checker(arg: str):
        try:
            f = value_type(arg)
        except ValueError:
            raise argparse.ArgumentTypeError(f'must be a valid {value_type}')
        if f < min_value or f > max_value:
            raise argparse.ArgumentTypeError(f'must be within [{min_value}, {min_value}]')
        return f

    # Return function handle to checking function
    return range_checker

Examples

You can use it for both floats and ints, but is not limited to that:

parser.add_argument('--float_example',
                    type=ranged_type(float, 0.0, 1.0))
parser.add_argument('--int_example',
                    type=ranged_type(int, -5, 5))

Even works with strings

parser.add_argument('--str_example',
                    type=ranged_type(str, "a", "d"))

Test

A little test/proof-of-concept:

parser = argparse.ArgumentParser(prog='argtest')
parser.add_argument('--float_example',
                    type=ranged_type(float, 0.0, 1.0))
parser.add_argument('--int_example',
                    type=ranged_type(int, -5, 5))
parser.add_argument('--str_example',
                    type=ranged_type(str, "a", "d"))

print(parser.parse_args())

With the following results:

> python argtest.py --float_example 0.5 --str_example b --int_example 4

Namespace(float_example=0.5, int_example=4, str_example='b')


> python argtest.py --float_example -0.5

usage: argtest [-h] [--float_example FLOAT_EXAMPLE] [--int_example INT_EXAMPLE] [--str_example STR_EXAMPLE]
argtest: error: argument --float_example: must be within [0.0, 1.0]


> python argtest.py --int_example 12    

usage: argtest [-h] [--float_example FLOAT_EXAMPLE] [--int_example INT_EXAMPLE] [--str_example STR_EXAMPLE]
argtest: error: argument --int_example: must be within [-5, 5]


> python argtest.py --str_example g 

usage: argtest [-h] [--float_example FLOAT_EXAMPLE] [--int_example INT_EXAMPLE] [--str_example STR_EXAMPLE]
argtest: error: argument --str_example: must be within [a, d]


like image 37
Jens C. Thuren Lindahl Avatar answered Nov 04 '22 10:11

Jens C. Thuren Lindahl