类型注解(Type Annotation)和运行时反射(Runtime Reflection)是现代 Python 工程的两块基石。前者让代码"会说话",后者让程序在运行时"照镜子"。两者结合,催生了 Pydantic、FastAPI、dataclasses 这类改变 Python 生态的框架。理解它们,是从"能写 Python"到"会设计 Python 系统"的关键一跃。
一、类型注解:代码的"说明书"
它到底是什么
Python 本质上是动态类型语言------变量没有固定类型。但从 Python 3.5 起,PEP 484 引入了类型注解语法,允许开发者在代码里标注变量、参数、返回值的期望类型:
python
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
这里的 : str、: int、-> str 就是类型注解。关键点 :Python 解释器本身不强制执行这些注解,它们只是"元数据",存储在对象的 __annotations__ 属性里。真正的类型检查由 mypy、pyright 等静态分析工具,或 Pydantic 这类运行时库来完成。
__annotations__ 的本质
每个函数、类,都有一个 __annotations__ 字典:
python
def add(x: int, y: int) -> int:
return x + y
print(add.__annotations__)
# {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
类也一样:
python
class Point:
x: float
y: float
label: str = "origin"
print(Point.__annotations__)
# {'x': <class 'float'>, 'y': <class 'float'>, 'label': <class 'str'>}
这个字典是运行时反射的入口------所有框架魔法的起点。
二、typing 模块:构建复杂类型的工具箱
typing 模块提供了远超内置类型的表达能力,核心概念如下:
泛型容器
python
from typing import List, Dict, Tuple, Set
def process(items: List[int]) -> Dict[str, int]:
return {str(i): i for i in items}
Python 3.9+ 可以直接用内置类型:list[int]、dict[str, int],不再需要从 typing 导入。
联合类型与可选类型
python
from typing import Union, Optional
# Union:可以是 int 或 str
def parse(value: Union[int, str]) -> str:
return str(value)
# Optional[X] 等价于 Union[X, None]
def find_user(user_id: Optional[int] = None) -> Optional[str]:
...
Python 3.10+ 引入了更优雅的写法:int | str、int | None。
TypeVar:泛型函数
python
from typing import TypeVar, List
T = TypeVar('T')
def first(items: List[T]) -> T:
return items[0]
TypeVar 让函数能"保留"类型信息------输入 List[int],返回值也知道是 int,而不是模糊的 Any。
Literal、Final、TypedDict
python
from typing import Literal, Final, TypedDict
# Literal:限定为特定值
Mode = Literal["read", "write", "append"]
# Final:声明常量
MAX_RETRY: Final = 3
# TypedDict:带类型的字典结构
class UserInfo(TypedDict):
name: str
age: int
email: str
Protocol:结构子类型(鸭子类型的类型化)
python
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("Drawing circle")
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # ✅ 无需继承 Drawable,只要有 draw 方法即可
Protocol 是 Python 类型系统里最优雅的设计之一,完美契合 Python 的鸭子类型哲学。
三、运行时反射:程序的"自我审视"
typing.get_type_hints():正确读取注解的方式
直接读 __annotations__ 有个大坑------前向引用(Forward Reference):
python
class Node:
next: "Node" # 用字符串避免循环引用
print(Node.__annotations__)
# {'next': 'Node'} ← 只是字符串,不是真正的类型!
get_type_hints() 会自动解析这些字符串引用,返回真实的类型对象:
python
from typing import get_type_hints
hints = get_type_hints(Node)
# {'next': <class '__main__.Node'>} ← 真正的类型
这正是 Pydantic 等框架在底层做的事情。
inspect 模块:深度解剖对象
inspect 是 Python 自省(Introspection)的瑞士军刀,核心 API 一览:
python
import inspect
# 1. 获取函数签名
def create_user(name: str, age: int, admin: bool = False) -> dict:
...
sig = inspect.signature(create_user)
for param_name, param in sig.parameters.items():
print(f"{param_name}: 注解={param.annotation}, 默认值={param.default}")
# 输出:
# name: 注解=<class 'str'>, 默认值=<class 'inspect._empty'>
# age: 注解=<class 'int'>, 默认值=<class 'inspect._empty'>
# admin: 注解=<class 'bool'>, 默认值=False
python
# 2. 获取类的成员
class MyClass:
class_var: int = 0
def method(self): ...
@staticmethod
def static_method(): ...
# 过滤出所有方法
methods = inspect.getmembers(MyClass, predicate=inspect.isfunction)
# 3. 判断对象类型
inspect.isclass(MyClass) # True
inspect.isfunction(create_user) # True
inspect.ismethod(obj.method) # True(绑定方法)
# 4. 获取源码
print(inspect.getsource(create_user))
# 5. 获取调用栈
frame = inspect.currentframe()
print(inspect.getframeinfo(frame))
四、两者结合:框架魔法的底层逻辑
理解了注解和反射,很多"魔法"框架就不再神秘。下面用一个简化的依赖注入容器演示:
python
import inspect
from typing import get_type_hints, Callable, Any, Dict, Type
class Container:
"""极简依赖注入容器"""
_registry: Dict[type, Any] = {}
@classmethod
def register(cls, service_type: type, instance: Any):
cls._registry[service_type] = instance
@classmethod
def resolve(cls, func: Callable) -> Any:
hints = get_type_hints(func)
sig = inspect.signature(func)
kwargs = {}
for param_name, param in sig.parameters.items():
if param_name == 'return':
continue
param_type = hints.get(param_name)
if param_type and param_type in cls._registry:
kwargs[param_name] = cls._registry[param_type]
return func(**kwargs)
# 使用
class Database:
def query(self): return "data"
class UserService:
def __init__(self, db: Database):
self.db = db
Container.register(Database, Database())
# 框架自动识别 UserService.__init__ 需要 Database,并注入
FastAPI 的路由参数解析、Pydantic 的模型验证,本质上都是这套机制的工业级实现。
五、核心概念关系图

六、工程实践:该怎么用,怎么避坑
✅ 该做的
渐进式引入注解:不必一次全加,从公共 API、核心数据结构开始标注,收益最大。
用 get_type_hints() 而非直接读 __annotations__ :前者能正确处理 from __future__ import annotations(Python 3.10+ 的延迟求值模式)带来的字符串化注解问题。
善用 inspect.signature() 做参数校验:写工具函数、装饰器时,用签名反射来动态适配不同函数,比硬编码参数名健壮得多。
Protocol 优于抽象基类 :当你只关心"对象能做什么"而非"对象是什么"时,Protocol 更灵活,也更 Pythonic。
⚠️ 要注意的
注解不等于运行时约束 :def f(x: int) 传入字符串不会报错,需要 Pydantic 或手动校验才能在运行时强制类型。
from __future__ import annotations 的副作用 :开启后所有注解变成字符串,__annotations__ 里看不到真实类型,必须用 get_type_hints() 解析,且解析时需要正确的命名空间。
inspect 有性能开销 :getmembers、getsource 等操作涉及文件 I/O 或大量遍历,不要在热路径(高频调用的代码)里使用,适合初始化阶段或调试工具。
循环引用问题 :类互相引用时,get_type_hints() 可能抛出 NameError,需要传入正确的 localns / globalns 参数:
python
hints = get_type_hints(MyClass, globalns=globals(), localns=locals())
七、一张表总结核心 API
| 工具 | 核心 API | 典型用途 |
|---|---|---|
typing |
List, Dict, Optional, Union |
标注容器与可选类型 |
typing |
TypeVar, Generic |
编写泛型函数/类 |
typing |
Protocol |
定义结构化接口 |
typing |
get_type_hints() |
运行时安全读取注解 |
inspect |
signature() |
获取函数参数及默认值 |
inspect |
getmembers() |
枚举类/模块的所有成员 |
inspect |
isclass() / isfunction() |
判断对象类型 |
inspect |
getsource() |
获取源码文本(调试用) |
类型注解和运行时反射,本质上是 Python 在"动态灵活"与"工程可靠"之间找到的平衡点。注解让意图可见,反射让框架智能,两者叠加,才有了今天 Python 生态里那些"写起来像魔法、用起来很踏实"的工具。掌握这套机制,你就掌握了读懂 FastAPI、Pydantic、SQLModel 等现代框架源码的钥匙。
参考来源
- Python Discussions --- Questionable annotation behaviors & runtime introspection scope: discuss.python.org/t/questiona...
- Python 官方文档 ---
typingmodule: docs.python.org/3/library/t... - Medium --- Reading Type Hints at Runtime: What
__annotations__Actually Contains: medium.com/@tihomir.ma... - Pydantic GitHub Issue --- Runtime type introspection &
get_type_hints: github.com/pydantic/py...