I would like to define some generic decorators to check arguments before calling some functions.
Something like:
@checkArguments(types = ['int', 'float']) def myFunction(thisVarIsAnInt, thisVarIsAFloat) ''' Here my code ''' pass
Side notes:
EDIT 2021: funny that type checking did not go antipythonic in the long run with type hinting and mypy.
To extract the number and names of the arguments from a function or function[something] to return ("arg1", "arg2"), we use the inspect module. The given code is written as follows using inspect module to find the parameters inside the functions aMethod and foo.
Implementing Decorator Arguments You may expect that decorator arguments are somehow passed into the function along with this f argument, but sadly Python always passes the decorated function as a single argument to the decorator function.
Python Decorators Summary Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated. Using decorators in Python also ensures that your code is DRY(Don't Repeat Yourself).
A decorator in Python is a function that takes another function as its argument, and returns yet another function . Decorators can be extremely useful as they allow the extension of an existing function, without any modification to the original function source code.
From the Decorators for Functions and Methods:
Python 2
def accepts(*types): def check_accepts(f): assert len(types) == f.func_code.co_argcount def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t), \ "arg %r does not match %s" % (a,t) return f(*args, **kwds) new_f.func_name = f.func_name return new_f return check_accepts
Python 3
In Python 3 func_code
has changed to __code__
and func_name
has changed to __name__
.
def accepts(*types): def check_accepts(f): assert len(types) == f.__code__.co_argcount def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t), \ "arg %r does not match %s" % (a,t) return f(*args, **kwds) new_f.__name__ = f.__name__ return new_f return check_accepts
Usage:
@accepts(int, (int,float)) def func(arg1, arg2): return arg1 * arg2 func(3, 2) # -> 6 func('3', 2) # -> AssertionError: arg '3' does not match <type 'int'>
arg2
can be either int
or float
On Python 3.3, you can use function annotations and inspect:
import inspect def validate(f): def wrapper(*args): fname = f.__name__ fsig = inspect.signature(f) vars = ', '.join('{}={}'.format(*pair) for pair in zip(fsig.parameters, args)) params={k:v for k,v in zip(fsig.parameters, args)} print('wrapped call to {}({})'.format(fname, params)) for k, v in fsig.parameters.items(): p=params[k] msg='call to {}({}): {} failed {})'.format(fname, vars, k, v.annotation.__name__) assert v.annotation(params[k]), msg ret = f(*args) print(' returning {} with annotation: "{}"'.format(ret, fsig.return_annotation)) return ret return wrapper @validate def xXy(x: lambda _x: 10<_x<100, y: lambda _y: isinstance(_y,float)) -> ('x times y','in X and Y units'): return x*y xy = xXy(10,3) print(xy)
If there is a validation error, prints:
AssertionError: call to xXy(x=12, y=3): y failed <lambda>)
If there is not a validation error, prints:
wrapped call to xXy({'y': 3.0, 'x': 12}) returning 36.0 with annotation: "('x times y', 'in X and Y units')"
You can use a function rather than a lambda to get a name in the assertion failure.
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