基於 Python 3 新增的函數注解(Function Annotations )語法實現參數類型檢查功能


 

2016-01-06

函數注解(Function Annotations)

函數注解語法 可以讓你在定義函數的時候對參數和返回值添加注解:

def foobar(a: int, b: "it's b", c: str = 5) -> tuple:
    return a, b, c
  • a: int 這種是注解參數
  • c: str = 5 是注解有默認值的參數
  • -> tuple 是注解返回值。

注解的內容既可以是個類型也可以是個字符串,甚至表達式:

def foobar(a: 1+1) -> 2 * 2:
    return a

那么如何獲取我們定義的函數注解呢?至少有兩種辦法:

  • __annotations__:

    >>> foobar.__annotations__
    {'a': int, 'b': "it's b", 'c': str, 'return': tuple}
    
  • inspect.signature:

    >>> import inspect >>> sig = inspect.signature(foobar) >>> # 獲取函數參數 >>> sig.paraments mappingproxy(OrderedDict([('a', <Parameter "a:int">), ('b', <Parameter "b:"it's b"">), ('c', <Parameter "c:str=5">)])) >>> # 獲取函數參數注解 >>> for k, v in sig.parameters.items(): print('{k}: {a!r}'.format(k=k, a=v.annotation)) a: <class 'int'> b: "it's b" c: <class 'str'> >>> # 返回值注解 >> sig.return_annotation tuple 

既然可以得到函數中定義的注解,那么我們就可以用它進行參數類型檢查了。

類型檢查

Python 解釋器並不會基於函數注解來自動進行類型檢查,需要我們自己去實現類型檢查功能:

>>> foobar.__annotations__
{'a': int, 'b': "it's b", 'c': str, 'return': tuple}

>>> foobar(a='a', b=2, c=3)
('a', 2, 3)

既然通過 inspect.signature 我們可以獲取函數定義的參數的順序以及函數注解, 那么我們就可以通過定義一個裝飾器來檢查傳入函數的參數的類型是否跟函數注解相符, 這里實現的裝飾器函數(check_type.py)如下:

# coding: utf8
import collections import functools import inspect def check(func): msg = ('Expected type {expected!r} for argument {argument}, ' 'but got type {got!r} with value {value!r}') # 獲取函數定義的參數 sig = inspect.signature(func) parameters = sig.parameters # 參數有序字典 arg_keys = tuple(parameters.keys()) # 參數名稱 @functools.wraps(func) def wrapper(*args, **kwargs): CheckItem = collections.namedtuple('CheckItem', ('anno', 'arg_name', 'value')) check_list = [] # collect args *args 傳入的參數以及對應的函數參數注解 for i, value in enumerate(args): arg_name = arg_keys[i] anno = parameters[arg_name].annotation check_list.append(CheckItem(anno, arg_name, value)) # collect kwargs **kwargs 傳入的參數以及對應的函數參數注解 for arg_name, value in kwargs.items(): anno = parameters[arg_name].annotation check_list.append(CheckItem(anno, arg_name, value)) # check type for item in check_list: if not isinstance(item.value, item.anno): error = msg.format(expected=item.anno, argument=item.arg_name, got=type(item.value), value=item.value) raise TypeError(error) return func(*args, **kwargs) return wrapper 

下面來測試一下我們的裝飾器:

@check
def foobar(a: int, b: str, c: float = 3.2) -> tuple:
    return a, b, c

順序傳參測試:

>>> foobar(1, 'b')
(1, 'b', 3.2)

>>> foobar(1, 'b', 3.5)
(1, 'b', 3.5)

>>> foobar('a', 'b')
...
TypeError: Expected type <class 'int'> for argument a, but got type <class 'str'> with value 'a

>>> foobar(1, 2)
...
TypeError: Expected type <class 'str'> for argument b, but got type <class 'int'> with value 2

>>> foobar(1, 'b', 3)
...
TypeError: Expected type <class 'float'> for argument c, but got type <class 'int'> with value

關鍵字傳參:

>>> foobar(b='b', a=2)
(2, 'b', 3.2)
>>> foobar(b='b', a=2, c=3.5)
(2, 'b', 3.5)

>>>foobar(a='foo', b='bar')
...
TypeError: Expected type <class 'int'> for argument a, but got type <class 'str'> with value 'foo'

>>> foobar(b=3, a=2)
...
TypeError: Expected type <class 'str'> for argument b, but got type <class 'int'> with value 3

>>> foobar(a=2, b='bar', c=3)
...
TypeError: Expected type <class 'float'> for argument c, but got type <class 'int'> with value

借助於 Function Annotations 一個簡單的參數類型檢查的裝飾器就這樣實現了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM