Clean lightweight type-checking with Python 3.0 annotations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | def typecheck(func):
if not hasattr(func,'__annotations__'): return method
import inspect
argspec = inspect.getfullargspec(func)
def check( t, T ):
if type(T) == type: return isinstance(t,T) #types
else: return T(t) #predicates
def wrapper(*args):
if len(argspec.args) != len(args):
raise TypeError( "%s() takes exactly %s positional argument (%s given)"
%(func.__name__,len(argspec.args),len(args)) )
for argname,t in zip(argspec.args, args):
if argname in func.__annotations__:
T = func.__annotations__[argname]
if not check( t, T ):
raise TypeError( "%s( %s:%s ) but received %s=%s"
% (func.__name__, argname,T, argname,repr(t)) )
r = func(*args)
if 'return' in func.__annotations__:
T = func.__annotations__['return']
if not check( r, T ):
raise TypeError( "%s() -> %s but returned %s"%(func.__name__,T,repr(r)) )
return r
return wrapper
|
With Python 3000's function annotations method signature type checking is much cleaner than with Python 2.x (see at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/426123).
This recipe provides a very minimal implementation of type and predicate checking.
The typical usage for this decorator would be something like this:
@typecheck
def foo(i: int) -> bool:
return a > 0
is_even = lambda x: x % 2 == 0
@typecheck
def multiply_by_2(i: int) -> is_even:
return i * 2