Search code examples
pythonargparse

argparse - Define custom actions or types with additional arguments


I'm developing a toolbox containing several python scripts. For several of them some arguments may be numeric values. Depending of the script some may require a value v to be between -1 and 1, or 0 and 1 or 1 and 10 or ... An example could be a page width from an output diagram which should be always positive.

I can check all the time if v is in the required range. I could also for each of these range define an Action or a type using argparse. An example is given using a new type:

def positive_num(a_value):
    """Check a numeric positive."""
    if not a_value > 0:
        raise argparse.ArgumentTypeError("Should be positive.")
    return a_value 

And add it later to the parser:

parser_grp.add_argument('-pw', '--page-width',
                        help='Output pdf file width (e.g. 7 inches).',
                        type=positive_num,
                        default=None,
                        required=False)

Now, if the value is a correlation coefficient (or anything in a range) would it be possible using action or types to write something more general using:

def ranged_num(a_value, lowest=-1, highest=1):
    """Check a numeric is in expected range."""
    if not (a_value >= lowest and a_value <= highest):
        raise argparse.ArgumentTypeError("Not in range.")
    return a_value 

That could later be added like:

parser_grp.add_argument('-c', '--correlation',
                        help='A value for the correlation coefficient',
                        type=ranged_num(-1,1),
                        default=None,
                        required=False)

I have tried in several ways but whithout success.

Thank you


Solution

  • Per the documentation:

    type= can take any callable that takes a single string argument and returns the converted value

    Therefore, to use it like type=ranged_num(-1,1), your ranged_num function must return a function itself. A function that returns a function (or accepts a function as an argument, or both) is often referred to as a "higher-order function".

    Here's a minimal example:

    def ranged_num(lowest=-1, highest=1):
        """Check a numeric is in expected range."""
        def type_func(a_value):
            a_value = int(a_value)  # or "float"; you could also have error handling here
            if not (a_value >= lowest and a_value <= highest):  # I'd rewrite this to an "or"
                raise argparse.ArgumentTypeError("Not in range.")
            return a_value
        return type_func
    

    Now ranged_num creates and returns a function, type_func, that is responsible for handling the string coming from the command line.