函数注解
变量注解
inspect
pydantic
注解
python语言的缺点
- python是动态语言,变量可被赋值不同类型,不能做类型检查,变量的类型是在运行期间决定的
- 解决方案
- 增加文档说明docmentation string
- py3.5引入函数注解
- py3.6引入变量注解
初识函数注解和变量注解
def foo(x: int, y: int) -> tuple: # 函数注解,非强制
z: list[int] = [1] # 变量注解,非强制
z.append(y)
return x + y, z
print(foo(1, 2))
print(foo('a', 'b'))
# print(foo(1, 'c')) # 不能够进行类型检查,报错TypeError: unsupported operand type(s) for +: 'int' and 'str'
print(foo.__annotations__) # 函数注解保存在__annotations__属性中
---------------------------------------------------------------------
(3, [1, 2])
('ab', [1, 'b'])
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'tuple'>}
- 函数注解:对函数的参数和返回类型进行类型注解
- 变量注解:对变量进行注解,同时仍可指定默认值
- 注解只是一个辅助说明,并不对函数参数进行类型检查,对变量进行强制要求
- 函数注解信息保存在
__annotations__
属性中,返回值为字典类型
注解的作用
- 对参数进行辅助说明
- 提供给第三方工具做代码分析,比如类型检查器、集成开发环境、静态检查器等
- 在IDE中能够代码联想
函数注解
参考文档
基本语法
- 函数参数
def foo(a: expression, b: expression = 5):
...
def foo(*args: expression, **kwargs: expression):
...
def foo((x1, y1: expression),
(x2: expression, y2: expression)=(None, None)):
...
使用
:
语句将信息附加到变量或函数参数中
- 返回值
def sum() -> expression:
...
->
运算符用于将信息附加到函数/方法的返回值中
- lambda表达式不支持注解
- 函数注解信息保存在
__annotations__
属性中,返回值为字典类型,包括返回值类型的声明
变量注解
from typing import ClassVar, Dict
class Starship:
captain: str = 'Picard'
damage: int
stats: ClassVar[Dict[str, int]] = {}
def __init__(self, damage: int, captain: str = None):
self.damage = damage
if captain:
self.captain = captain # Else keep the default
def hit(self):
Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1
enterprise_d = Starship(3000)
enterprise_d.stats = {} # Flagged as error by a type checker
Starship.stats = {} # This is OK
- 同一函数范围内注释受全局变量global或非局部变量nonlocal影响的变量是非法的
def f():
global x: int # SyntaxError
def g():
x: int # Also a SyntaxError
global x
类型检查
通过上面的分析,python 运行时并不强制标注函数和变量类型,注解也不会进行类型检查。接下来介绍几种类型检查的方式
inspect模块
提供获取对象信息的函数,可以检查函数和类、类型检查
文档
inspect使用
- inspect.signature(callable):获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)
import inspect
def add(x: int, y: int, *args, **kwargs) -> int:
return x + y
sig = inspect.signature(add)
print(1, add.__annotations__)
print(2, sig, type(sig)) # 函数签名
print('3 params : ', sig.parameters) # OrderedDict
print('4 return : ', sig.return_annotation)
print(5, sig.parameters['y'], type(sig.parameters['y']))
print(6, sig.parameters['x'].annotation)
print(7, sig.parameters['args'])
print(8, sig.parameters['args'].annotation) # 无注解 <class 'inspect._empty'>
print(9, sig.parameters['kwargs'])
print(10, sig.parameters['kwargs'].annotation)
--------------------------------------
1 {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
2 (x: int, y: int, *args, **kwargs) -> int <class 'inspect.Signature'>
3 params : OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
4 return : <class 'int'>
5 y: int <class 'inspect.Parameter'>
6 <class 'int'>
7 *args
8 <class 'inspect._empty'>
9 **kwargs
10 <class 'inspect._empty'>
- is函数
inspect.isfunction(add),是否是函数
inspect.ismethod(add)),是否是类的方法
inspect.isgenerator(add)),是否是生成器对象
inspect.isgeneratorfunction(add)),是否是生成器函数
inspect.isclass(add)),是否是类
inspect.ismodule(inspect)),是否是模块
inspect.isbuiltin(print)),是否是内建对象
还有很多is函数,需要的时候查阅inspect模块帮助
- Parameter对象
- 保存在元组中,是只读的
- name,参数的名字
- annotation,参数的注解,可能没有定义
- default,参数的缺省值,可能没有定义
- _empty,特殊的类,用来标记default属性或者注释annotation属性的空值
- kind,实参如何绑定到形参,就是形参的类型
- POSITIONAL_ONLY,值必须是位置参数提供
- POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供
- VAR_POSITIONAL,可变位置参数,对应
*args
- KEYWORD_ONLY,keyword-only参数,对应
*
或者*args
之后的出现的非可变关键字参数 - VAR_KEYWORD,可变关键字参数,对应
**kwargs
import inspect
def add(x, y: int = 7, *args, z, t=10, **kwargs) -> int:
return x + y
sig = inspect.signature(add)
print(sig)
print('params : ', sig.parameters) # 有序字典
for i, item in enumerate(sig.parameters.items()):
name, param = item
print(i + 1, name, param.annotation, param.kind, param.default)
print(param.default is param.empty, end='\n\n')
类型检查示例
import inspect
def check(fn):
def wrapper(*args, **kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i, p in enumerate(args):
param = values[i]
if param.annotation is not param.empty and not isinstance(p, param.annotation):
print(p, '!==', values[i].annotation)
for k, v in kwargs.items():
if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
print(k, v, '!===', params[k].annotation)
return fn(*args, **kwargs)
return wrapper
@check
def add(x, y: int = 7) -> int:
return x + y
add(20, 10)
add(20, y=10)
add(y=10, x=20)
pydantic模块
- pydantic官方手册
- 示例1: models和ValidationError
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name = 'John Doe'
signup_ts: Optional[datetime] = None
friends: List[int] = []
external_data = {
'id': '123',
'signup_ts': '2019-06-01 12:22',
'friends': [1, 2, '3'],
}
user = User(**external_data)
print(user.id)
print(repr(user.signup_ts))
print(user.friends)
print(user.dict())
try:
User(signup_ts='broken', friends=[1, 2, 'not number'])
except ValidationError as e:
print(e)
-----------------------------------------------
{'id': 123, 'signup_ts': datetime.datetime(2019, 6, 1, 12, 22), 'friends': [1, 2, 3], 'name': 'John Doe'}
3 validation errors for User
id
field required (type=value_error.missing)
signup_ts
invalid datetime format (type=value_error.datetime)
friends -> 2
value is not a valid integer (type=type_error.integer)
- 示例2:
@validator
自定义errors
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
name: str
username: str
password1: str
password2: str
@validator('name')
def name_must_contain_space(cls, v):
if ' ' not in v:
raise ValueError('must contain a space')
return v.title()
@validator('password2')
def passwords_match(cls, v, values, **kwargs):
if 'password1' in values and v != values['password1']:
raise ValueError('passwords do not match')
return v
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
user = UserModel(
name='samuel colvin',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn',
)
print(user) # name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
UserModel(
name='samuel',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn2',
)
except ValidationError as e:
print(e)
"""
2 validation errors for UserModel
name
must contain a space (type=value_error)
password2
passwords do not match (type=value_error)
"""
更多用法可参考pydantic官方手册
其他
扩展阅读
参考
- magedu