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