Last active
September 9, 2017 14:06
-
-
Save macrat/5d347ad7056ada77e03e6fec39d2041f to your computer and use it in GitHub Desktop.
pythonの型タイピングを使った動的なassert、の、構想。
This file contains hidden or 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 functools | |
import inspect | |
import typing | |
def istype(obj: typing.Any, typ: type) -> bool: | |
""" | |
>>> istype(1, int) | |
True | |
>>> istype('hoge', int) | |
False | |
>>> istype([1, 2, 3], typing.List) | |
True | |
>>> istype([1, 2, 3], typing.List[int]) | |
True | |
>>> istype(['a', 'b', 3], typing.List[str]) | |
False | |
>>> istype({'a': 1, 'b': 2}, typing.Dict[str, int]) | |
True | |
>>> istype({'a': 1, 'b': 2.0}, typing.Dict[str, int]) | |
False | |
>>> istype({'a': 1, 0xb: 2}, typing.Dict[str, int]) | |
False | |
>>> istype(1, typing.Union[int, float]) | |
True | |
>>> istype('1', typing.Union[int, float]) | |
False | |
>>> istype(1, typing.Optional[int]) | |
True | |
>>> istype(1.0, typing.Optional[int]) | |
False | |
>>> istype(1.0, typing.Optional[int]) | |
False | |
>>> istype((1, 'a'), typing.Tuple[int, str]) | |
True | |
>>> istype((1, 2), typing.Tuple[int, str]) | |
False | |
>>> istype((1, 2), typing.Tuple[int, ...]) | |
True | |
>>> istype((1, 'a'), typing.Tuple[int, ...]) | |
False | |
""" | |
try: | |
return isinstance(obj, typ) or typ is typing.Any | |
except TypeError: | |
if not hasattr(typ, '__origin__'): | |
return False | |
if typ.__origin__ is typing.List: | |
return (isinstance(obj, typ.__origin__) | |
and all(istype(k, typ.__args__[0]) for k in obj)) | |
elif typ.__origin__ is typing.Dict: | |
return (isinstance(obj, typ.__origin__) | |
and all(istype(k, typ.__args__[0]) for k in obj.keys()) | |
and all(istype(k, typ.__args__[1]) for k in obj.values())) | |
elif typ.__origin__ is typing.Union: | |
return any(istype(obj, t) for t in typ.__args__) | |
elif typ.__origin__ is typing.Optional: | |
return istype(obj, typ.__args__[0]) or obj is None | |
elif typ.__origin__ is typing.Tuple: | |
if not isinstance(obj, typ.__origin__): | |
return False | |
elif typ.__args__[1] == Ellipsis: | |
return all(istype(o, typ.__args__[0]) for o in obj) | |
else: | |
return all(istype(o, t) for o, t in zip(obj, typ.__args__)) | |
else: | |
raise NotImplementedError(f'{typ.__origin__} was not supported yet') | |
def typing_assert(func: typing.Callable) -> typing.Callable: | |
""" | |
>>> @typing_assert | |
... def func_a(x: int, y: float) -> str: | |
... return 'hoge' | |
... | |
>>> func_a(1, 2.0) | |
'hoge' | |
>>> func_a(1.0, 2.0) | |
Traceback (most recent call last): | |
... | |
TypeError: func_a: x (1th argument) must be int, but got float | |
>>> @typing_assert | |
... def func_b() -> str: | |
... return 1 | |
... | |
>>> func_b() | |
Traceback (most recent call last): | |
... | |
TypeError: func_b: excepts return str, but got int | |
""" | |
spec = inspect.getfullargspec(func) | |
@functools.wraps(func) | |
def wrap(*args, **kwds): | |
for i, arg_name in enumerate(spec.args): | |
if not istype(args[i], spec.annotations[arg_name]): | |
raise TypeError(f'{func.__name__}: {arg_name} ({i + 1}th argument) must be {spec.annotations[arg_name].__name__}, but got {type(args[i]).__name__}') | |
result = func(*args, **kwds) | |
if not istype(result, spec.annotations['return']): | |
raise TypeError(f'{func.__name__}: excepts return {spec.annotations["return"].__name__}, but got {type(result).__name__}') | |
return result | |
return wrap |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment