A typesafe wrapper for namedtuple.
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 35  | 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
def Record(sig,*types):
   cls_name, _, cls_vars = sig.partition(' ')
   import collections
   cls = collections.namedtuple(cls_name,cls_vars)
   cls_vars = cls_vars.split(' ')
   assert len(types)==len(cls_vars)
   cls.__new__.__annotations__ = dict(zip(cls_vars,types))
   cls.__new__ = typecheck(cls.__new__)
   return cls
 | 
I had been torn between haskell and python until I thought of this recipe. Now if I just had a static type-checker for Python...
#example usage: an sql record
import re
re_email = re.compile('^[_.0-9a-z-]+@([0-9a-z][0-9a-z-]+.)+[a-z]{2,4}$')
def is_email(s): 
   return isinstance(s,str) and re_email.match(s) 
User = Record('User email pwdhash',is_email,str)
u = User('me@mysite.com','ajfs80')
u.email
u.pwdhash
      
Download
Copy to clipboard