Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check parameters of a function in a Python

For example, I have a function, that generates procedural noise

def procedural_noise(width, height, seed):
   ...

All of the parameters of this function should be positive. I suppose, that me need to check it and throw exception if on of parameters is non-positive. Is it a good (pythonic way) approach?

Let us suppose, that I am right. Which is the best way to check parameters?


I can write the checkers for each of parameters:

def procedural_noise(width, height, seed):
    if width <= 0:
        raise ValueError("Width should be positive")
    if height <= 0:
        raise ValueError("Height should be positive")
    if seed <= 0:
        raise ValueError("Seed should be positive")
    ...

It should be clearly for programmer when he will get exception, what he should to correct, but it's not good-looking in my opinion.

The following code is easier, but it is too far from ideal:

def procedural_noise(width, height, seed):
    if width <= 0 or height <= 0 or seed <= 0:
        raise ValueError("All of the parameters should be positive")
    ...

The last question: which is the best way to write tests with unittest framework, that checks types of parameters and their values?

I can write the following function in a test class:

def test_positive(self):
    self.assertRaises(ValueError, main.procedural_noise, -10, -10, 187)

Is it a correct solution?


UPD: I upvoted all answers, because each of them have a useful information for me, but I can't select the best answers of them (I suppose that it is fair to select most voted question tomorrow)

like image 422
Queue Overflow Avatar asked Apr 11 '15 22:04

Queue Overflow


1 Answers

Also this could be a nice use case for function annotations (in Python 3). Example:

import inspect
from functools import wraps    

def positive(name, value):
    if value < 0:
        raise ValueError(name + " must be positive")

def check_params(f):
    signature = inspect.signature(f)
    @wraps(f)
    def wrapper(*args, **kwargs):
        bound_arguments = signature.bind(*args, **kwargs)
        for name, value in bound_arguments.arguments.items():
            annotation = signature.parameters[name].annotation
            if annotation is inspect.Signature.empty:
                continue
            annotation(name, value)
        return f(*args, **kwargs)
    return wrapper

@check_params
def procedural_noise(width: positive, height: positive, seed: positive):
    pass # ...

It's a little bit of inspect-fu in the check_params decorator (inspired by github.com/ceronman/typeannotations), but provides pretty nice and flexible way to check function arguments - without any ifs, fors or other noise-code in the type-checked functions.

like image 102
Messa Avatar answered Sep 24 '22 01:09

Messa