46. 定義帶參數的裝飾器

要求:實現一個裝飾器,它用來檢查被裝飾函數的參數類型。裝飾器可以通過參數指明函數參數的類型,調用時如果檢測出類型不匹配則拋出異常。

@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 inspect

def 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,str
def f(a, b, c):
    pass

f(5, [], 'abc')
f(5, 10, 'abc')             #類型不匹配會報錯

結果:

Traceback (most recent call last):
  File "h:\Python Practice\twenty-four\c1.py", line 22, in <module>
    f(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 <class 'list'>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章