Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Parameter checking with Exception Raising

I am attempting to write exception raising code blocks into my Python code in order to ensure that the parameters passed to the function meet appropriate conditions (i.e. making parameters mandatory, type-checking parameters, establishing boundary values for parameters, etc...). I understand satisfactorily how to manually raise exceptions as well as handling them.

from numbers import Number

def foo(self, param1 = None, param2 = 0.0, param3 = 1.0):
   if (param1 == None):
      raise ValueError('This parameter is mandatory')
   elif (not isinstance(param2, Number)):
      raise ValueError('This parameter must be a valid Numerical value')
   elif (param3 <= 0.0):
      raise ValueError('This parameter must be a Positive Number')
   ...

This is an acceptable (tried and true) way of parameter checking in Python, but I have to wonder: Since Python does not have a way of writing Switch-cases besides if-then-else statements, is there a more efficient or proper way to perform this task? Or is implementing long stretches of if-then-else statements my only option?

like image 639
S. Gamgee Avatar asked Jul 28 '16 13:07

S. Gamgee


People also ask

How do I check if an exception is raised in Python?

There are two ways you can use assertRaises: using keyword arguments. Just pass the exception, the callable function and the parameters of the callable function as keyword arguments that will elicit the exception. Make a function call that should raise the exception with a context.

How do you handle a raised exception in Python?

In Python, exceptions can be handled using a try statement. The critical operation which can raise an exception is placed inside the try clause. The code that handles the exceptions is written in the except clause. We can thus choose what operations to perform once we have caught the exception.

How do you pass an exception as an argument in Python?

If you write the code to handle a single exception, you can have a variable follow the name of the exception in the except statement. If you are trapping multiple exceptions, you can have a variable follow the tuple of the exception.

What happens when exception is raised Python?

When an exception is raised, no further statements in the current block of code are executed. Unless the exception is handled (described below), the interpreter will return directly to the interactive read-eval-print loop, or terminate entirely if Python was started with a file argument.


2 Answers

You could create a decorator function and pass the expected types and (optional) ranges as parameters. Something like this:

def typecheck(types, ranges=None):
    def __f(f):
        def _f(*args, **kwargs):
            for a, t in zip(args, types):
                if not isinstance(a, t):
                    raise TypeError("Expected %s got %r" % (t, a))
            for a, r in zip(args, ranges or []):
                if r and not r[0] <= a <= r[1]:
                    raise ValueError("Should be in range %r: %r" % (r, a))
            return f(*args, **kwargs)
        return _f
    return __f

Instead of if ...: raise you could also invert the conditions and use assert, but as noted in comments those might not always be executed. You could also extend this to allow e.g. open ranges (like (0., None)) or to accept arbitrary (lambda) functions for more specific checks.

Example:

@typecheck(types=[int, float, str], ranges=[None, (0.0, 1.0), ("a", "f")])
def foo(x, y, z):
    print("called foo with ", x, y, z)
    
foo(10, .5, "b")  # called foo with  10 0.5 b
foo([1,2,3], .5, "b")  # TypeError: Expected <class 'int'>, got [1, 2, 3]
foo(1, 2.,"e")  # ValueError: Should be in range (0.0, 1.0): 2.0
like image 99
tobias_k Avatar answered Nov 06 '22 07:11

tobias_k


I think you can use decorator to check the parameters.

def parameterChecker(input,output):
...     def wrapper(f):
...         assert len(input) == f.func_code.co_argcount
...         def newfun(*args, **kwds):
...             for (a, t) in zip(args, input):
...                 assert isinstance(a, t), "arg {} need to match {}".format(a,t)
...             res =  f(*args, **kwds)
...             if not isinstance(res,collections.Iterable):
...                 res = [res]
...             for (r, t) in zip(res, output):
...                 assert isinstance(r, t), "output {} need to match {}".format(r,t)   
...             return f(*args, **kwds)
...         newfun.func_name = f.func_name
...         return newfun
...     return wrapper

example:
@parameterChecker((int,int),(int,))
... def func(arg1, arg2):
...     return '1'
func(1,2)
AssertionError: output 1 need to match <type 'int'>

func(1,'e')
AssertionError: arg e need to match <type 'int'>
like image 1
galaxyan Avatar answered Nov 06 '22 05:11

galaxyan