Skip to content

Instantly share code, notes, and snippets.

@macrat
Last active September 9, 2017 14:06
Show Gist options
  • Save macrat/5d347ad7056ada77e03e6fec39d2041f to your computer and use it in GitHub Desktop.
Save macrat/5d347ad7056ada77e03e6fec39d2041f to your computer and use it in GitHub Desktop.
pythonの型タイピングを使った動的なassert、の、構想。
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