Welcome, guest | Sign In | My Account | Store | Cart

Clean lightweight type-checking with Python 3.0 annotations.

Python, 34 lines
 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