Skip to content

Instantly share code, notes, and snippets.

@badocelot
Created October 10, 2017 16:08
Show Gist options
  • Save badocelot/f1df22136e1f28cbfe3c665595d562d2 to your computer and use it in GitHub Desktop.
Save badocelot/f1df22136e1f28cbfe3c665595d562d2 to your computer and use it in GitHub Desktop.
Runtime type-checking for Python (unfinished from 2014)
import inspect
def name_of_type (t):
if "__class__" in dir(t):
return t.__class__.__name__
else:
return type(args[i]).__name__
def argtypes(*__argtypes_types__, **__argtypes_kwtypes__):
"""decorates a function to be type-checked at runtime
use None to disable type-checking for an argument or a return type
type will check for both built-in types and classes
"""
# had to give this function's arguments ridiculous names to reduce
# collisions in kwtypes
types = __argtypes_types__
kwtypes = __argtypes_kwtypes__
def typechecker(fn):
def checkedfn (*args, **kwargs):
name = fn.__name__
n_args = len(types)
n_given = len(args)
if n_given != n_args:
raise TypeError(name + " expected " + str(n_args) +
" arguments, got " + str(n_given))
for i in range(n_args):
if types[i] == type:
if not inspect.isclass(args[i]):
raise TypeError(name + ": argument " + str(i) +
" expected to be a type or class, got " +
name_of_type(args[i]))
elif types[i] != None and not isinstance(args[i], types[i]):
raise TypeError(name + ": argument " + str(i) +
" expected to be of type " + types[i].__name__ + ", got " +
name_of_type(args[i]))
for kw in kwargs:
if kw in kwtypes and kwtypes[kw] != None and \
not isinstance(kw, kwtypes[kw]):
raise TypeError(name + ": argument " + repr(kw) +
"expected to be of type " + kwtypes[kw].__name__ +
", got " + name_of_type(kwargs[kw]))
return fn(*args, **kwargs)
checkedfn.__name__ = fn.__name__
return checkedfn
# first ensure that we have a sequence of types
if not isinstance(types, (tuple, list)):
raise TypeError("typechecked: argument 0 expected a tuple or list" +
", got " + name_of_type(types))
# ensure that arguments types are valid types or None
# TODO: allow for tuples, lists, and sets
for t in types:
if t != None and not inspect.isclass(t):
raise TypeError(repr(t) + " is not a type, class, or None")
# ensure that the keyword argument types are valid types or None
# TODO: allow for tuples, lists, and sets
for kw in kwtypes:
if kwtypes[kw] != None and not inspect.isclass(kwtypes[kw]):
raise TypeError("type of kwarg " + repr(kw) + " (" + repr(t) +
") is not a type, class, or None")
return typechecker
def returntype (return_type):
def typechecker(fn):
def checkedfn(*args, **kwargs):
name = fn.__name__
value = fn(*args, **kwargs)
if not isinstance(value, return_type):
raise TypeError(name + " expected to return type " +
str(return_type) + " but returned a " + name_of_type(value))
else:
return value
checkedfn.__name__ = fn.__name__
return checkedfn
# ensure the return_type is a valid type or None
# TODO: allow for tuples, lists, and sets
if return_type != None and not inspect.isclass(return_type):
raise TypeError("returntype: argument 'return_type' expected to be" +
"a type, class; got " + name_of_type(return_type))
return typechecker
# for applying typechecked to instance methods without having to set the
# self type explicitly to None
def method_argtypes (*__methodargtypes_types__, **__methodargtypes_kwtypes__):
# had to give this function's arguments ridiculous names to reduce
# collisions in kwtypes
types = __methodargtypes_types__
kwtypes = __methodargtypes_kwtypes__
return argtypes(*((None,) + types), **kwtypes)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment