类型提示
Python语言通过标准库的typing模型,提供类型提示机制,但是Python解释器不理会这些类型信息的,实际运行时候,相当于这些信息是不存在的。所以即便你传递了一个错误类型,编译器也不会报错(有些工具可以给些提示)。
Python开发者是可以看到这些提示信息的,利用这些信息可以实现更多功能,比如检查类型错误。Pydantic包就是这样的软件包。Pydantic和FastAPI结合,能够大幅简化Web开发工作中许多事物。
类型提示可激活IDE的代码补全功能。
类型信息提示具体由三种写法:
- 针对变量
- 函数形参
- 函数返回值,
- 基本标量类型
在变量名后加冒号例如 name: type name: type = value
x: int, y: int
- 容器类型
变量如果是集合类型,其中部件也可以做类型注解。比如 name:dict[keytype,valuetype]={key1:val1,key2:val2}
nums: List[int], mapping: Dict[str, float]
大小写等价,即list[int] 等同于List[int]
-
可选&默认
def greet(name: str | None = None) -> str:
-
不定长参数
def log(msg: str, *args: int, **kw: float) -> None:
-
Any (typing)表示任何一种类型
def a(x: Any) -> int:
-
Union 表示类型中某一种,Union[str,int],表示str或者int
def u(x: Union[int, str]) -> int:
-
限制类型范围(TypeVar 边界)
python
Num = TypeVar('Num', int, float) # 指定为int float 这2种
def add(a: Num, b: Num) -> Num:
变量为空的三种写法
python
from typing import Union, Optional
def example1(var : Union[float,None]):
pass
def example2(var : Optional[float,None]):
pass
def example3(var : float|None):
pass
基本语法:在Python中声明可为空变量的三种等价写法:
- Union[float, None]:明确指出联合类型
- Optional[float]:更简洁的写法,等同于Union[float,None]
- float | None:Python 3.10引入的现代写法
为类型添加额外信息
Annotated 是Python的typing模块中的一个工具,用来为类型添加元数据或附加信息。
通常是给类型贴便签 ------解释器不管,静态工具/运行时库可以读,用来做单位、校验、依赖注入等元数据。typing.Annotated[type, *metadata]
- 基本用法:Annotated[Type,metadata1,metadata2,...]
- Type: 这里是被注解的类型。比如int str List[int]等
- metadata1,metadata2,...这些是附加类型元数据,可以是任何表达式,通常是一些类或字符串,用于提供附加信息。
例如:
直接从函数种读取元数据
python
from typing import Annotated
def log_usage(func_name: str, param: Annotated[int, "用户ID"]):
print(f"[{func_name}] 用户 {param} 被访问")
# 静态工具或框架可以读取这个元数据
print(log_usage.__annotations__)
# {'func_name': <class 'str'>, 'param': typing.Annotated[int, '用户ID']}
通过typing.get_type_hints获取类型提示
python
import typing
Age = typing.Annotated[int, "this is an age value"]
def set_age(age: Age):
print(f"set age to {age}")
Age = Annotated[int,"this is an age value"] 表示一个整数类型int,附加一个字符串元数据。
元数据不影响int基本行为,但可以用于文档或类型检查工具。
运行时可以把标签获取出来
typing.get_type_hints(..., include_extras=True)
返回带 __metadata__
的构造:
python
hints = typing.get_type_hints(set_age, include_extras=True)
print(hints['age'])
print(hints['age'].__metadata__)
以上代码打印结果:
python
typing.Annotated[int, 'this is an age value']
('this is an age value',)
第三方库直接给你封装好:
-
pydantic ≥1.10 自动识别
Annotated[..., Field(...)]
-
FastAPI ≥0.95 用
Annotated[Type, Depends(...)]
代替旧版Depends
-
typer 用
Annotated[str, typer.Argument(help='xxx')]
-
typeguard 、beartype 支持
Annotated[str, IsRegex]
等自定义约束
使用 Annotated
元数据
例子:用 pydantic 做"区间 + 单位"校验
python
from typing import Annotated
from pydantic import BaseModel, Field, conint
class Config:
# 1. 用 Field 写约束
timeout: Annotated[int, Field(gt=0, le=30, description="seconds")]
# 2. 用 conint 也行
retry: Annotated[conint(ge=1, le=5), "max retry"]
cfg = Config(timeout=15, retry=3)
pydantic 只认 Field
/conint
这类自己定义的 metadata,普通字符串当文档。
例子:FastAPI 0.95+ 全新依赖注入写法
python
from fastapi import FastAPI, Depends, Annotated
app = FastAPI()
async def get_current_user(token: str) -> User:
...
@app.get("/whoami")
def whoami(user: Annotated[User, Depends(get_current_user)]):
return {"name": user.name}
例子:自己写装饰器 把函数元数据拿出来运行
python
from typing import Annotated
import inspect
# 1. 定义一个"校验函数"
def validate_positive(x: int) -> bool:
if x <= 0:
raise ValueError("必须为正数")
return True
# 2. 把函数当作元数据贴上去
def deposit(
amount: Annotated[int, validate_positive] # ← 函数当元数据
) -> str:
return f"存入 {amount} 元"
# 3. 手动把元数据取出来并调用
sig = inspect.signature(deposit)
param = sig.parameters['amount']
validator = param.annotation.__metadata__[0] # 拿到 validate_positive
validator(100) # 正常
validator(-10) # 触发 ValueError
运行结果:
ValueError: 必须为正数
→ 你看到:元数据是函数,但除非你显式拿它调用,否则什么也不会发生。
例子:借助第三方库 让框架自动帮你调用
typeguard
的下一个版本(≥ 4.2)已支持把 Annotated
里的函数当成运行时校验器 ;
这里用 annotated-types
+ pydantic
演示一种社区通行做法:
python
from typing import Annotated
from annotated_types import Gt
def _not_zero(x: int) -> int:
if x == 0:
raise ValueError("不能为 0")
return x
# 把函数包成"类型适配器"
NotZero = Annotated[int, _not_zero]
from pydantic import BaseModel, TypeAdapter
ta = TypeAdapter(NotZero)
ta.validate_python(5) # 正常
ta.validate_python(0) # ValueError: 不能为 0
函数元数据被 Pydantic 的 TypeAdapter 取出来并执行。
例子:自己写装饰器消费 metadata
python
from typing import Annotated, get_args, get_origin
import inspect
def _check_range(val, meta):
for m in meta:
if hasattr(m, 'min') and not (m.min <= val <= m.max):
raise ValueError(f'{val} out of range [{m.min}, {m.max}]')
def validate(func):
sig = inspect.signature(func)
hints = typing.get_type_hints(func, include_extras=True)
for name, param in sig.parameters.items():
annotation = hints[name]
if get_origin(annotation) is Annotated:
typ, *meta = get_args(annotation)
# 把元数据存起来,调用时检查
param._meta = meta
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
for name, val in bound.arguments.items():
meta = getattr(sig.parameters[name], '_meta', ())
_check_range(val, meta)
return func(*args, **kwargs)
return wrapper
class ValueRange:
def __init__(self, min, max):
self.min, self.max = min, max
@validate
def set_age(age: Annotated[int, ValueRange(0, 120)]):
print("age =", age)
set_age(150) # ValueError: 150 out of range [0, 120]