
Python 的类型注解,比你想象的更强大。它不只是给 IDE 看的提示,更是一个可以在运行时被读取、被解释、被执行的元数据容器。本文从「一个注解,三种用途」切入,揭秘 Python 运行时、mypy 静态检查、FastAPI 动态验证各自如何消费注解;深入
Annotated的底层实现,手写依赖注入装饰器,带你理解 FastAPI 推荐风格的核心原理。读完你会明白:为什么说 Python 的类型注解是「便利贴」------语言不管,但框架玩得飞起。
一个注解,三种用途
类型注解编译后保留在
__annotations__中,Python 运行时不管它,mypy 在运行前用它做静态检查,FastAPI/pydantic 在运行时主动读取它做动态验证。 同一个注解,三个东西各自用。
arduino
源码: def add(a: int, b: int) -> int: ...
│
▼ 编译
字节码: 保留注解 → __annotations__ = {'a': int, 'b': int, 'return': int}
│
┌─────────┼─────────┐
▼ ▼ ▼
运行时 mypy FastAPI / pydantic
忽略 静态检查 动态读取注解做验证/转换
| 角色 | 怎么用注解 |
|---|---|
| Python 运行时 | 完全不看,只存着 |
| mypy / pyright | 静态分析,不运行代码,检查类型是否一致 |
| FastAPI / pydantic | 运行时读取 __annotations__,用它做参数验证和转换 |
python
def get_user(user_id: int) -> str:
...
# 1. 运行时:Python 不管
get_user("abc") # 照跑,但逻辑可能崩
# 2. 静态检查:mypy 分析源码
# mypy 报错:Argument 1 to "get_user" has incompatible type "str"; expected "int"
# 3. 动态读取:FastAPI 运行时用 __annotations__ 做验证
# 自动把 "123" 转成 123,如果传 "abc" 则返回 422 错误
Java视角深入理解Python类型注解的设计哲学
Python 的类型注解是便利贴------贴在那,谁想读谁读,做什么随你。Python 选择了极度自由,代价是框架各自造轮子
Python 把"注解是什么意思"这个责任,从语言层面推给了库作者。 好处是极度灵活,代价是不看文档就不知道注解的实际行为
python
def f(x: int):
...
# FastAPI 的理解:x 是查询参数,从 URL 解析并转成 int
# pydantic 的理解:x 必须是 int,否则验证失败并报错
# mypy 的理解:x 应该传 int,我来静态检查
# Python 的理解:x 随便是什么,关我屁事
Annotated
给类型贴标签的容器
元数据可以是任何对象 ------字符串、类、函数、Depends 实例,Python 不管,由框架自己解释。
python
from typing import Annotated
# 语法:Annotated[真实类型, 元数据1, 元数据2, ...]
x: Annotated[int, "范围 0-100", "必填"]
# │ │ │
# │ └── 元数据(任何 Python 对象)
# └── 真实类型
# 拆开看
x.__origin__ # <class 'int'>
x.__metadata__ # ('范围 0-100', '必填')
其中 __metadata__ 是其特有属性
python
# 检查是否为 Annotated 类型
if hasattr(param_type, '__metadata__'):
# 提取元数据
metadata = param_type.__metadata__
一个简单案例,利用函数本身的元数据来给自己的函数参数赋值
主要是熟悉怎么获取函数的 Annotated 类型注解,以及获取它的元数据
python
import functools
from typing import Annotated, get_type_hints
def happy_birthday(name: Annotated[str, ("在深圳", 18), "希望你天天开心"],
age,
address,
msg) -> str:
"""通过获取 Annotated 里面的元数据类进行赋值"""
if age and address and msg:
print(f"🎂 祝 {name} {age}岁生日快乐!你现在在{address},{msg}")
else:
print("happy_birthday", name, age, address, msg)
print(f"祝福已发送给 {name}")
# {'name': typing.Annotated[str, ('在深圳', 18), '希望你天天开心'], 'return': <class 'str'>}
hints = get_type_hints(happy_birthday, include_extras=True)
for name, annotation in hints.items():
# 获取 Annotated 类型注解
if hasattr(annotation, "__metadata__"):
(address, age), msg = getattr(annotation, "__metadata__")
happy_birthday = functools.partial(happy_birthday, age=age, address=address, msg=msg)
happy_birthday("Pkmer")
"""
🎂 祝 Pkmer 18岁生日快乐!你现在在在深圳,希望你天天开心
祝福已发送给 Pkmer
"""
利用类型注解实现依赖注入
模仿 FastAPI 推荐的 Annotated,实现依赖注入 user: Annotated[dict, DependsOn(get_user)]
python
import functools
import inspect
from collections.abc import Callable
from typing import ParamSpec, TypeVar, Annotated, get_type_hints
P = ParamSpec('P')
R = TypeVar('R')
class DependsOn:
def __init__(self, func: Callable):
self.func = func
def my_inject(func):
"""依赖注入装饰器"""
sig = inspect.signature(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数"""
# 获取函数的参数类型
parameters = sig.parameters
# include_extras 对 Annotated 携带元数据的支持,获取所有参数类型
hints = get_type_hints(func, include_extras=True)
# 先绑定用户先传递的参数
bound = sig.bind_partial(*args, **kwargs)
# 遍历参数类型,处理 DependsOn
for name, annotation in parameters.items():
# annotation_type = annotation.annotation
annotation_type = hints.get(name)
# 找到 Annotated 类型注解
if annotation_type and hasattr(annotation_type, "__metadata__"):
for metadata in annotation_type.__metadata__:
if isinstance(metadata, DependsOn) and name not in bound.arguments:
bound.arguments[name] = metadata.func()
print(bound.arguments)
return func(*bound.args, **bound.kwargs)
return wrapper
def get_user() -> dict:
return {"name": "Pkmer"}
@my_inject
def greet(user: Annotated[dict, DependsOn(get_user)],
book="Learn Python Programming 3rd Edition",
*,
msg: str):
print(f"{user.get("name")} {msg} {book}")
greet(msg="在深圳,听歌")
