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?
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.
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.
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.
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.
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
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'>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With