Created
October 10, 2017 16:08
-
-
Save badocelot/f1df22136e1f28cbfe3c665595d562d2 to your computer and use it in GitHub Desktop.
Runtime type-checking for Python (unfinished from 2014)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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