要求:实现一个装饰器,它用来检查被装饰函数的参数类型。装饰器可以通过参数指明函数参数的类型,调用时如果检测出类型不匹配则抛出异常。
@type_assert(str, int, int)def f(a, b, c): ... @type_assert(y=list)def g(x, y): ...
解决方案:
提取函数签名:inspect.signature()
函数。
带参数的装饰器,也就是根据参数定制化一个装饰器,可以看成生产装饰器的工厂(装饰器可以看成是生产被装饰函数的工厂)。每次调用带参数的装饰器,返回一个特定的装饰器,然后使用该特定的装饰器去装饰其他函数。
- 对于
inspect.signature()
函数:
inspect.signature(callable, *, follow_wrapped=True)
返回给定可调用对象的Signature对象。inspect.Signature
类方法:
Signature.bind(*args, **kwargs)
创建从位置和关键字参数到参数的映射,绑定所有参数。如果*arg
和**kwarg
匹配签名则返回BoundArguments,否则引发TypeError。
bind_partial(*args, **kwargs)
与Signature.bind()
相同,但是允许省略一些必需的参数(类似functools.partial()
),绑定部分参数。返回BoundArguments,如果传递的参数与签名不匹配,则引发TypeError。
>>> import inspect>>> def f(a, b, c): pass>>> f_sig = inspect.signature(f)>>> f_sig.parameters mappingproxy(OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b">), ('c', <Parameter "c">)]))>>> pa = f_sig.parameters['a']>>> pa.name'a'>>> pa.kind<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
>>> f_sig.bind(int, int, str)<BoundArguments (a=<class 'int'>, b=<class 'int'>, c=<class 'str'>)>>>> ba = f_sig.bind(int, int, str)>>> ba.arguments OrderedDict([('a', <class 'int'>), ('b', <class 'int'>), ('c', <class 'str'>)])>>> ba.arguments['a']<class 'int'>
- 方案示例:
import inspectdef type_assert(*ty_args, **ty_kwargs): def decorator(func): func_sig = inspect.signature(func) #获取函数签名对象 bind_type = func_sig.bind_partial(*ty_args, **ty_kwargs).arguments #以参数为key建立OrderedDict,value为绑定的type def wrap(*args, **kwargs): for name, obj in func_sig.bind(*args, **kwargs).arguments.items(): #obj是参数name的对象 type_ = bind_type.get(name) if type_: if not isinstance(obj, type_): raise TypeError('%s must be %s' % (name, type_)) return func(*args, **kwargs) return wrap return decorator @type_assert(int, list, str) #指定类型分别为int,list,strdef f(a, b, c): passf(5, [], 'abc')f(5, 10, 'abc') #类型不匹配会报错
结果:
Traceback (most recent call last): File "h:\Python Practice\twenty-four\c1.py", line 22, inf(5, 10, 'abc') File "h:\Python Practice\twenty-four\c1.py", line 12, in wrap raise TypeError('%s must be %s' % (name, type_)) TypeError: b must be