python3の例外のfromキーワード(前回の続き)
前回の反省を踏まえて書き直しました。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 関数の引数型を検証します。 """ from functools import wraps from inspect import getfullargspec, getmembers, isfunction class ArgumentError(Exception): def __init__(self, arg_name, val, typ): err = '引数 {} の {} は {} と型が違います。'.format(arg_name, val, typ) super().__init__(err) class RetValError(Exception): def __init__(self, retval, typ): err = '戻り値 {} は型が {} ではありません。'.format(retval, typ) super().__init__(err) class UndefinedArgTypeError(KeyError): def __init__(self): err = '引数の型が未定義です。' super().__init__(err) class UndefinedRetvalTypeError(KeyError): def __init__(self): err = '戻り値の型が未定義です。' super().__init__(err) def validate(func): """ >>> @validate ... def test(a: int, b: str) -> str: ... val = str(a) + b ... return val ... >>> test(1, "3") '13' """ anno = func.__annotations__ def check_type(key, value): try: obj_type = anno[key] except KeyError as err: if key == "return": raise UndefinedRetvalTypeError from err else: raise UndefinedArgTypeError from err def raise_error(): if key == "return": raise RetValError(value, obj_type) else: raise ArgumentError(key, value, obj_type) if type(obj_type) in (list, tuple, set): if type(value) not in obj_type: raise_error() else: if obj_type is None: obj_type = type(obj_type) if type(value) is not obj_type: raise_error() @wraps(func) def _validate(*args, **kwargs): argSpec = getfullargspec(func) for name, value in zip(argSpec.args, args): if name == "self": continue check_type(name, value) result = func(*args, **kwargs) if func.__name__ != "__init__": check_type("return", result) return result return _validate def validate_method(cls): """ >>> @validate_method ... class Hoge: ... def fuga(self, a: int, b: float) -> float: ... return a + b >>> h = Hoge() >>> h.fuga(7, 0.777) 7.777 """ for name, func in getmembers(cls, isfunction): func.__name__ = name func.__doc__ = getattr(cls, name).__doc__ setattr(cls, name, validate(func)) return cls def test(): import doctest doctest.testmod() if __name__ == "__main__": test()
前回の問題点はクラスデコレータ使うとデコったクラスがオブジェクトになってしまうところでこれは何かと問題があります。デコったクラスがクラスでは無くなってしまうので。
ということで普通に関数でデコレートしてクラスはそのまま返すことに。
デコレータの中でメソッドを上書きしてやることにしました。
表題の件はpython3から例外キャッチしたときにfrom句が使えるみたいで、実際例外を出してみるとどうなるかわかります。
>>> from validation import validate >>> @validate ... def a(b): ... pass ... >>> a(1) Traceback (most recent call last): File "./validation.py", line 47, in check_type obj_type = anno[key] KeyError: 'b' The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "./validation.py", line 75, in _validate check_type(name, value) File "./validation.py", line 52, in check_type raise UndefinedArgTypeError from err validation.UndefinedArgTypeError: '引数の型が未定義です。'
超適当ですがこんな感じです。
The above exception was the direct cause of the following exception:
って出てますね。自作の例外クラス作った時なんかは追いやすくなりそうです。
ちなみにfrom使わないと
During handling of the above exception, another exception occurred:
って出ます。python2ではこのような表示は出ないのでpython3のが便利になっているのがわかります。