最近在学习FastAPI的使用,发现其极力推广使用类型注释,其中的pydantic
提供的 BaseModel
更能够进行一些参数验证。如果我们只写了一个函数,如何根据函数类型注释来实现检测功能呢?
函数注解(Function Annotations)
函数注解语法 可以让你在定义函数的时候对参数和返回值添加注解:
1 | def foobar(a: int, b="it's b", c: str = 5) -> tuple: |
a: int
这种是注解参数c: str = 5
是注解有默认值的参数-> tuple
是注解返回值。
注解的内容既可以是个类型也可以是个字符串,甚至表达式:
1 | def foobar(a: 1+1) -> 2 * 2: |
那么如何获取我们定义的函数注解呢?至少有三种办法:
get_type_hints
1
2
3
4
5
6
7
8from typing import get_type_hints
def foobar(a: int, b="it's b", c: str = 5) -> tuple:
return a, b, c
print(get_type_hints(foobar))
__annotations__
:1
2In [18]: foobar.__annotations__
Out[18]: {'a': <class 'int'>, 'c': <class 'str'>, 'return': <class 'tuple'>}inspect.signature
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21In [22]: import inspect
In [23]: sig = inspect.signature(foobar)
# 通过签名获取函数参数
In [24]: sig.parameters
Out[24]:
OrderedDict([('a', <Parameter "a: int">), ('b', <Parameter "b="it's b"">), ('c', <Parameter "c: str = 5">)])
# 获取函数参数注解
In [25]: for k, v in sig.parameters.items():
...: print('{k}: {a!r}'.format(k=k, a=v.annotation))
...:
a: <class 'int'>
b: <class 'inspect._empty'>
c: <class 'str'>
# 返回值注解
In [26]: sig.return_annotation
Out[26]: tuple
既然可以得到函数中定义的注解,那么我们就可以用它进行参数类型检查了。
类型检查
Python 解释器并不会基于函数注解来自动进行类型检查,需要我们自己去实现类型检查功能:
1 | In [27]: foobar.__annotations__ |
使用 inspect.signature
既然通过 inspect.signature
我们可以获取函数定义的参数的顺序以及函数注解, 那么我们就可以通过定义一个装饰器来检查传入函数的参数的类型是否跟函数注解相符, 这里实现的装饰器函数如下:
1 | import collections |
下面来测试一下我们的装饰器
顺序传参测试:
1 | In [30]: @check |
关键字传参:
1 | In [34]: foobar(b='b', a=2) |
借助于Function Annotations
一个简单的参数类型检查的装饰器就这样实现了。
使用 get_type_hints
1 | from typing import get_type_hints |
同样我们写个装饰器:
1 | from functools import wraps |
getfullargspec 获取到位置参数和关键字参数
相比使用函数签名使用这种方式更加简洁