Skip to main content

Chapter 51: Typing and Runtime Boundary

Python 的类型标注把“程序员希望对象满足什么形状”写进源码,CPython runtime 仍按对象、名字绑定、属性查找、调用协议和异常路径执行代码。本章要建立的判断能力是:看到 typingTypedDictProtocol、泛型别名和 annotation 时,能够区分哪些结论来自静态类型检查器,哪些状态真实存在于运行时对象上,哪些校验需要工程代码显式承担。

这个边界会直接影响框架设计。CLI 参数、HTTP payload、插件接口、ORM model、序列化层和配置系统都可能使用类型标注表达契约。类型检查器能在提交前发现一批调用错误,runtime 只会在执行到真实对象操作时暴露缺字段、缺方法、值类型错误或协议不匹配。

本章使用一个小型加载场景贯穿说明。它包含 Protocol 表达能力、TypedDict 表达字典结构、Generic 表达容器内部元素类型,并在最后用 runtime 校验收束边界。

from typing import Generic, Protocol, TypeVar, TypedDict, cast, runtime_checkable

T = TypeVar("T")

class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value

class Payload(TypedDict):
id: int
name: str
tags: list[str]

class Reader(Protocol):
def read(self, size: int) -> bytes:
...

@runtime_checkable
class Closable(Protocol):
def close(self) -> None:
...

def load(reader: Reader, raw: dict[str, object]) -> Box[Payload]:
data = cast(Payload, raw)
return Box(data)

这段代码里有三类信息。第一类是 runtime 真实创建的对象,例如 Box 类对象、Payload 类型对象、Reader 类型对象、函数对象和 annotation 存储。第二类是静态工具使用的关系,例如 T 的一致性、Reader 的结构化能力、Payload 的键和值类型。第三类是工程边界,例如 cast(Payload, raw) 改变类型检查器的视角,传入的 raw 对象仍是普通 dict。Python 3.14 文档明确说明 runtime 不会强制执行函数和变量的类型标注,typing 模块提供的是 type hints 的 runtime 支撑对象和词汇表,类型结论通常由 type checker、IDE 和 linter 使用;这一点是整章的基础边界,参见 Python 3.14 typing 文档

51.1 typing, Generic, TypeVar, and Protocol

typing 是 Python 类型系统的标准词汇层。它提供 TypeVarGenericProtocolTypedDictcastget_type_hints 等对象,让源码可以表达“这个函数接收什么类型关系”“这个对象需要有什么方法”“这个容器内部元素应保持什么一致性”。这些对象的主要消费者是静态类型检查器,CPython runtime 只负责创建、保存和暴露相关对象。

TypeVar 表达类型变量。类型变量的作用是把多个位置绑定成同一个类型选择,例如 Box[T] 中的 T 同时约束构造参数、实例属性和返回位置。它解决的问题是“一处传入 str 后,相关位置也应是 str;一处传入 Payload 后,相关位置也应是 Payload”。

Generic 表达一个类可以被类型参数化。Python 3.12 起支持类型参数语法,例如 class Box[T]: ...;兼容旧版本时仍常见 T = TypeVar("T")class Box(Generic[T]): ...。Python 文档说明泛型类会提供 __class_getitem__(),因此 Box[int] 这类表达式能在 runtime 被求值。这个求值结果服务 annotation 和 introspection,实例对象本身仍按普通类实例运行。

from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value

number_box = Box[int]("runtime accepts this value")
print(type(number_box.value))

类型检查器会把 Box[int]("runtime accepts this value") 判为不一致,因为构造参数位置期望 int。CPython 执行这段代码时会调用 Box.__init__,把字符串对象绑定到 self.value。这里的 runtime 路径是普通函数调用和属性写入,T 没有进入对象字段校验路径。

Protocol 表达结构化类型关系。结构化类型关系的工作定义是:一个对象只要提供了目标能力,就可以被视为满足某个协议。Reader 协议要求对象有 read(size: int) -> bytes。类型检查器会检查传入对象是否具备兼容的 read 方法,runtime 调用时只会执行 reader.read(size);如果对象没有 read 属性,错误会在属性查找或调用时出现。

from typing import Protocol

class Reader(Protocol):
def read(self, size: int) -> bytes:
...

class MemoryReader:
def read(self, size: int) -> bytes:
return b"x" * size

def take(reader: Reader) -> bytes:
return reader.read(3)

print(take(MemoryReader()))

MemoryReader 没有显式继承 Reader,类型检查器仍可根据结构接受它。runtime 侧看到的是一个普通实例,函数体里的关键动作是属性查找 reader.read 和方法调用。这个例子把协议边界固定下来:静态层判断“对象形状是否满足协议”,执行层只处理真实属性和真实调用。

@runtime_checkable 会给 Protocol 增加有限的 runtime 查询能力。它允许 isinstance(obj, SomeProtocol) 这种检查,但检查内容是属性存在性,类型签名和属性值类型不会被完整验证。Python 3.12 以后,runtime-checkable protocol 的 isinstance 检查使用 inspect.getattr_static() 查找属性,并且 protocol 成员在类创建后被视为固定成员;这些版本细节来自 typing 文档中 runtime_checkable 的说明

from typing import Protocol, runtime_checkable

@runtime_checkable
class Closable(Protocol):
def close(self) -> None:
...

class Resource:
close = "not a method"

print(isinstance(Resource(), Closable))

这个检查可以返回 True,因为对象上存在名为 close 的属性。随后执行 Resource().close() 会因为字符串对象无法调用而失败。@runtime_checkable 适合做轻量能力探测,工程代码如果依赖方法可调用性、参数形状、返回值类型和副作用约束,就需要继续写显式校验或用测试覆盖真实调用路径。

在源码阅读时,可以按三个层级定位 typing 抽象:先看 annotation 里出现了哪些类型词汇,再看这些词汇对应的 runtime 对象是否会参与执行,最后看函数体里真实发生的属性查找、下标访问、调用和异常路径。大多数泛型、TypeVarProtocol 关系停留在 annotation 与工具层;对象行为仍由实例、类、descriptor、slot 和普通方法调用决定。

51.2 TypedDict and runtime data shape

TypedDict 用来表达字典的结构化数据形状。它的工作定义是:用类声明语法写出一组键及其值类型,让类型检查器把普通 dict 按结构化 payload 处理。它解决的是“字典字段缺失、字段拼写错误、字段值类型不一致”这类静态可发现问题。

在 runtime 中,TypedDict 实例就是普通 dict。Python 文档明确说明 TypedDict instances are simply dicts,并且键集合和值类型期望只由类型检查器强制执行,参见 TypedDict 文档。这使它适合表达跨函数、跨模块、跨接口的字典契约,但它本身不承担输入数据验证。

from typing import NotRequired, Required, TypedDict

class Payload(TypedDict):
id: int
name: str
tags: list[str]

class PartialPayload(TypedDict, total=False):
id: Required[int]
name: str
tags: NotRequired[list[str]]

payload: Payload = {"id": "wrong", "name": "book", "tags": [1, 2]}
print(type(payload))
print(payload["id"])

类型检查器会指出 idtags 的值类型与 Payload 声明不一致。CPython runtime 会创建一个字典对象,把字符串和整数列表照常存入。随后 payload["id"] 返回字符串对象。失败何时出现取决于后续代码对这个值做什么:如果后续执行整数运算,错误会在运算协议或函数调用中出现;如果后续只是序列化为 JSON,它可能继续流动到更远的边界。

RequiredNotRequiredtotal=False 影响静态键存在性判断,也会在 TypedDict 类型对象上留下可自省元数据。常见属性包括 __required_keys____optional_keys____total__。这些属性能帮助文档生成器、schema 生成器或校验器读取声明,但读取声明与验证真实数据是两个阶段。

from typing import NotRequired, Required, TypedDict

class PartialPayload(TypedDict, total=False):
id: Required[int]
name: str
tags: NotRequired[list[str]]

print(PartialPayload.__required_keys__)
print(PartialPayload.__optional_keys__)
print(PartialPayload.__total__)

这段代码读取的是 PartialPayload 类型对象上的元数据。它说明声明中哪些键被视为必需,哪些键被视为可选。它没有扫描某个具体 dict,也没有验证每个值的类型。工程上要把 TypedDict 变成 runtime 保证,需要额外写边界函数:先检查对象是不是 dict,再检查必需键,接着检查值类型,最后返回经过校验的对象或构造新的 domain object。

from typing import TypedDict

class Payload(TypedDict):
id: int
name: str
tags: list[str]

def parse_payload(raw: object) -> Payload:
if not isinstance(raw, dict):
raise TypeError("payload must be a dict")
if not isinstance(raw.get("id"), int):
raise TypeError("payload.id must be int")
if not isinstance(raw.get("name"), str):
raise TypeError("payload.name must be str")
tags = raw.get("tags")
if not isinstance(tags, list) or not all(isinstance(item, str) for item in tags):
raise TypeError("payload.tags must be list[str]")
return {"id": raw["id"], "name": raw["name"], "tags": tags}

这个函数把 TypedDict 的静态形状转成 runtime 检查。函数的返回值仍是 dict,但调用方可以把 parse_payload 视为信任边界:它接收未知 object,经过键和值检查后,返回满足 Payload 契约的数据。TypedDict 适合放在边界函数的返回 annotation 上,输入位置更适合写 objectdict[str, object],这样类型标注能表达“外部输入尚未被信任”。

TypedDict 与 class model 的关系也要固定。class Payload(TypedDict): ... 看起来像定义类,runtime 创建的是一个用于类型系统和元数据承载的特殊类对象;调用 Payload(id=1, name="x", tags=[]) 会得到普通字典。继承 TypedDict 可以合并字段,泛型 TypedDict 可以表达字段类型随类型参数变化。它们都服务数据形状声明,真实数据仍通过 dict 的哈希表、键查找、下标访问和修改规则运行。

排查数据形状问题时,先问三个问题:传入数据是否经过了 runtime 解析函数;类型检查器是否能看到 TypedDict annotation;后续代码是否把某个字段当成更具体类型使用。第一个问题决定运行安全,第二个问题决定提交前反馈,第三个问题决定错误触发点。

51.3 Static vs runtime typing

静态类型和 runtime 类型处在两个执行阶段。静态类型检查器读取源码、annotation、类型 stub、配置和控制流,输出诊断结论。CPython runtime 加载模块、创建对象、执行字节码、查找属性、调用函数、传播异常。两者使用同一份源码,但它们读取的信息、运行时机和失败表现不同。

下面的代码把这个差异压缩到一个例子里:

from typing import TypedDict, cast

class Payload(TypedDict):
id: int
name: str

raw: dict[str, object] = {"id": "100", "name": "book"}
payload = cast(Payload, raw)

print(payload["id"] + 1)

cast(Payload, raw) 会告诉类型检查器把 raw 视为 Payload。在 runtime 中,typing.cast 返回第二个参数本身,字典对象没有被复制,也没有被扫描。最后一行执行字符串与整数相加,错误来自二元运算协议。Python 文档对 cast() 的描述也是这个边界:它向 type checker 发出指定类型信号,runtime 返回原值且不执行检查,参见 typing.cast 文档

assert_typereveal_typeassert_never 等工具也要放回阶段边界。assert_type(value, Type) 要求类型检查器确认推断结果,runtime 返回第一个参数并且没有副作用。assert_never(value) 表达静态不可达分支,runtime 真执行到它时会抛异常。它们能帮助维护静态控制流结论,但它们无法替代输入解析、权限检查、长度限制或资源关闭。

类型检查器也会受可见性影响。它能分析源码中可见的 annotation、分支、赋值和导入;它对反射、动态属性注入、setattr、运行时生成类、条件导入、插件注册和 monkey patch 的理解会受配置和工具能力限制。runtime 对这些动态行为天然执行,因为执行路径上对象真实存在。工程设计要让关键契约尽量写在静态工具能读取的位置,例如公开函数签名、ProtocolTypedDictdataclass 字段和明确返回值。

runtime 类型则来自对象本身。type(obj) 读取对象的类型指针,isinstance(obj, cls) 沿类型层级、ABC hook 或 protocol runtime check 执行,属性访问进入 __getattribute__、descriptor 和实例字典路径。annotation 可以参与工具提示和框架逻辑,但普通 Python 运算不会自动读取 annotation 后再决定是否执行。

from typing import Any

value: int = "42"
unknown: Any = value

print(type(value))
print(unknown.upper())

value: int = "42" 对类型检查器是诊断点,对 CPython 是名字绑定。unknown: Any 会在静态层放宽后续检查,runtime 中 unknown 仍指向同一个字符串对象。Any 的作用是改变类型检查器的约束强度,它不会改变对象身份、对象类型或方法集合。

把静态结论迁移到 runtime 时,需要固定判断顺序。第一步,确认数据来源是否可信:内部构造值、外部请求、文件反序列化、环境变量和插件返回值的信任等级不同。第二步,确认 annotation 的消费者:类型检查器、文档生成器、框架 introspection 或 runtime validator。第三步,确认真实执行动作:属性查找、下标访问、方法调用、算术运算、序列化或 I/O。第四步,在边界位置写校验函数,并让校验函数返回更窄的静态类型。这样静态工具和 runtime 行为会形成闭环。

静态类型的价值在大型代码库里尤其明显。它能把许多接口不一致提前到编辑器、CI 和 review 阶段。runtime 校验的价值在信任边界上尤其明显。它能防止外部输入、版本漂移、跨语言数据和插件行为把错误对象带入核心逻辑。二者要按阶段组合:静态类型负责设计期反馈,runtime 校验负责执行期防线。

51.4 Annotation evaluation and generic alias

annotation 是函数、类和模块对象上的元数据。它可以表达类型,也可以被框架当成 schema、依赖注入、序列化或文档生成材料。理解 annotation 的关键是求值时机:同一段 annotation 表达式,在不同 Python 版本和 future import 下,可能在定义时求值、以字符串保存,或延迟到访问时求值。

Python 3 的 annotation 语义经历了三个主要模型。Python 3.0 到 3.13 的默认 stock semantics 会在定义处急切求值。Python 3.7 起可使用 from __future__ import annotations 把 annotation 保存为字符串。Python 3.14 起默认使用 deferred evaluation,annotation 表达式延迟到访问时求值;annotationlib 模块成为处理 annotation introspection 的低层入口。这个版本边界来自 annotationlib 文档的 Annotation semantics

class Node:
child: "Node | None"

print(Node.__annotations__)

在字符串化模型中,annotation 字典中可能保存字符串。使用 annotation 的框架若直接读取 __annotations__,会遇到字符串、真实类型、ForwardRef 或延迟求值产生的对象。Python 3.14 的 annotationlib.get_annotations() 支持以 VALUEFORWARDREFSTRING 等格式取回 annotation。VALUE 尝试求值并返回对象,遇到未定义名字可能抛错;FORWARDREF 允许未解析名字以 forward reference 形式存在;STRING 更适合文档展示。文档同时提醒大多数 annotation introspection 功能可能执行任意代码,参见 annotationlib 模块说明

from annotationlib import Format, get_annotations

class Node:
child: Node | None

print(get_annotations(Node, format=Format.STRING))

这段 Python 3.14 示例强调格式选择。文档生成器通常更适合 STRING,因为它关心展示;依赖注入容器若需要真实类型对象,可能选择 VALUE,并承担求值失败和执行任意代码的安全边界;schema 生成器遇到前向引用时,可以选择 FORWARDREF,再分阶段解析。

Python 3.10 到 3.13 中,访问 annotation 的推荐入口通常是 inspect.get_annotations();Python 3.14 起,annotationlib.get_annotations() supersedes inspect.get_annotations()。旧版本直接读取类的 __annotations__ 还有继承相关差异。官方 Annotations Best Practices 明确把版本分开说明,这对框架代码很关键:一个库若承诺支持 Python 3.10 到 3.14,就要把 annotation 读取封装在兼容层里。

generic alias 是另一个 runtime 可观察对象。list[int]dict[str, int]tuple[str, ...] 这类表达式会在 runtime 生成参数化泛型对象。对于内置容器,list[int] 的类型是 types.GenericAlias;它有 __origin____args____parameters__ 等只读属性。Python 标准类型文档说明 GenericAlias 主要用于 type annotations,并且 isinstance([1, 2], list[str]) 会抛 TypeError,参见 Generic Alias Type 文档

alias = dict[str, list[int]]

print(alias.__origin__)
print(alias.__args__)
print(type(alias))
print(alias({"a": [1, 2], "b": ["x"]}))

alias.__origin__ 指向未参数化的 dictalias.__args__ 保存参数。调用 alias(...) 会构造普通字典,元素类型不会被扫描。文档还说明参数化泛型在对象创建时会擦除类型参数,list[str]() 的结果类型仍是 list。这就是泛型别名的 runtime 边界:参数信息存在于 alias 对象上,容器实例没有自动携带逐元素类型约束。

annotation evaluation 和 generic alias 的共同点是“类型信息成为 runtime 可观察对象”。差异在于使用方式。annotation 挂在函数、类和模块上,需要按照版本语义和求值格式读取;generic alias 是表达式求值结果,可以直接检查 __origin____args__。两者都适合给工具、框架和文档系统提供材料;两者都不自动变成普通运算的类型守卫。

51.5 Runtime typing cost and boundary

runtime typing 的成本来自检查动作本身。检查一个普通 isinstance(x, int) 成本很低;递归检查 dict[str, list[Payload]] 需要遍历键、值、列表元素和嵌套字典;检查 Protocol 的属性存在性需要属性查找;解析 annotation 可能触发求值、导入、前向引用处理和异常路径。成本来源要落到对象分配、遍历深度、属性查找、函数调用和可能的用户代码执行上。

工程上要把类型信息分成三个使用层级。第一层是静态契约,例如函数签名、ProtocolTypedDict、泛型和类型别名。这一层服务 IDE、CI、review 和维护者理解,runtime 成本接近零。第二层是边界校验,例如解析 HTTP JSON、读取配置文件、反序列化消息、加载插件返回值。这一层需要显式检查,成本换取信任边界清晰。第三层是深度 runtime 类型系统,例如递归 validator、schema 框架、数据模型库和依赖注入容器。这一层功能更强,代价是额外遍历、错误聚合、转换规则、版本兼容和安全边界。

贯穿例子里的 load 函数可以改成两段式结构:外层解析未知输入,内层使用已经收窄的 Payload。这种结构让成本集中在入口,核心逻辑按可信对象执行。

from typing import Protocol, TypedDict

class Payload(TypedDict):
id: int
name: str
tags: list[str]

class Reader(Protocol):
def read(self, size: int) -> bytes:
...

def parse_payload(raw: object) -> Payload:
if not isinstance(raw, dict):
raise TypeError("payload must be a dict")
if not isinstance(raw.get("id"), int):
raise TypeError("payload.id must be int")
if not isinstance(raw.get("name"), str):
raise TypeError("payload.name must be str")
tags = raw.get("tags")
if not isinstance(tags, list) or not all(isinstance(item, str) for item in tags):
raise TypeError("payload.tags must be list[str]")
return {"id": raw["id"], "name": raw["name"], "tags": tags}

def handle_payload(payload: Payload) -> str:
return f"{payload['id']}:{payload['name']}:{len(payload['tags'])}"

parse_payload 是 runtime 边界,承担外部输入检查。handle_payload 是内部逻辑,依赖静态类型表达前置条件。这样排查错误时能快速定位:如果外部数据不满足结构,错误来自解析边界;如果内部逻辑对字段使用错误,类型检查器和测试应在更早阶段暴露。

何时使用 runtime 校验,可以按数据来源和失败成本判断。外部输入、持久化文件、网络消息、跨进程边界、插件返回值、反射构造对象和安全敏感参数需要 runtime 校验。内部私有函数、局部变量、中间迭代器、纯计算辅助函数和已经由入口校验过的对象,通常依赖静态类型、单元测试和代码审查即可。这个判断的核心不是“是否有 annotation”,而是“错误对象从哪里进入系统,失败后代价在哪里发生”。

何时依赖静态类型,也要看反馈链路。团队有类型检查 CI、编辑器能读取配置、依赖库提供 stub、动态行为被限制在小范围内时,静态类型能稳定降低接口漂移。项目大量使用动态属性注入、运行时生成函数、反射插件和字符串协议时,静态类型仍可覆盖核心接口,但要把动态部分包进窄接口,并在边界上用 runtime 校验补足。

runtime typing 还涉及安全边界。读取 annotation 可能执行表达式或触发导入;递归 validator 可能遍历攻击者控制的大对象;错误消息可能泄露内部结构;自动转换可能把坏数据转换成看似合法的对象。框架设计中,annotation introspection 要明确执行环境,输入校验要限制深度、大小和转换规则,错误返回要区分内部日志与对外消息。

本章最终的判断顺序可以压缩为五步。先看 annotation 表达的是名义类型、结构类型、字典形状、泛型关系还是元数据。再看它的消费者是 type checker、IDE、文档工具、框架 introspection 还是 runtime validator。接着看 runtime 对象上真实存在什么:类、实例、dictGenericAlias、annotation dict 或延迟求值函数。随后看错误对象可能从哪里进入系统。最后决定成本放置位置:设计期用静态类型,信任边界用显式校验,深层动态框架用受控的 introspection。

最小自检任务

阅读下面代码,判断每一行的 runtime 行为和类型检查器可能给出的结论。重点说明 PayloadPacket[int]cast@runtime_checkable 分别改变了哪一层状态。

from typing import Generic, Protocol, TypeVar, TypedDict, cast, runtime_checkable

T = TypeVar("T")

class Packet(Generic[T]):
def __init__(self, item: T) -> None:
self.item = item

class Payload(TypedDict):
id: int
tags: list[str]

@runtime_checkable
class HasClose(Protocol):
def close(self) -> None:
...

class Strange:
close = "closed"

raw: dict[str, object] = {"id": "7", "tags": [1, 2]}
payload = cast(Payload, raw)
packet = Packet[int](payload["id"])

print(type(payload))
print(type(packet.item))
print(isinstance(Strange(), HasClose))

答案要点

  • Payload 声明的是字典形状契约,runtime 中 payload 仍指向 raw 那个普通 dict 对象,因此 type(payload) 输出 dict
  • cast(Payload, raw) 改变类型检查器对 raw 的视角,runtime 返回原对象;它没有检查 id 是否为 int,也没有检查 tags 是否为 list[str]
  • 类型检查器可能在 raw 字面量赋给 dict[str, object] 时接受它,因为每个值都可作为 object;在 cast 之后,类型检查器会把 payload["id"] 视为 int
  • Packet[int](payload["id"]) 在静态层看起来满足 Packet[int],因为 payload["id"] 被视为 int;runtime 中 payload["id"] 实际是字符串对象,packet.item 保存字符串。
  • type(packet.item) 输出 str,说明泛型参数没有进入实例字段校验路径。
  • HasClose 使用 @runtime_checkable 后可以参与 isinstance,检查关注 close 属性存在性;Strange.close 是字符串属性,存在性检查可以通过。
  • 后续若执行 Strange().close(),错误会来自字符串不可调用;这属于真实调用路径错误,超出 @runtime_checkable 的轻量能力探测范围。
  • 稳定做法是把 raw 先交给解析函数检查键和值类型,再把返回值标注为 Payload,核心逻辑依赖静态类型表达已校验前提。

本章知识点总结

  • 类型边界:Python 类型标注主要服务静态工具,CPython runtime 仍按对象、绑定、查找、调用和异常路径执行。
  • TypeVar 关系TypeVar 把多个 annotation 位置绑定成同一个类型选择,runtime 不据此扫描实例字段。
  • 泛型对象Generic 让类可以参数化,Box[int] 等表达式可在 runtime 求值,但实例仍按普通类对象运行。
  • Protocol 结构Protocol 表达对象需要具备的能力,静态检查关注结构兼容,runtime 调用关注真实属性和方法。
  • runtime_checkable@runtime_checkable 允许协议进入 isinstance,检查重点是属性存在性,签名和值类型需要额外验证。
  • TypedDict 形状TypedDict 表达字典键和值类型,具体实例在 runtime 中仍是普通 dict
  • 键元数据__required_keys____optional_keys____total__ 可用于读取 TypedDict 声明,真实数据验证需要单独执行。
  • cast 边界typing.cast 改变静态视角,runtime 返回原对象,适合配合已经完成的外部校验使用。
  • annotation 版本:annotation 求值模型受 Python 版本和 future import 影响,Python 3.14 起默认延迟求值并提供 annotationlib
  • GenericAliaslist[int] 等表达式生成可自省的泛型别名,容器实例创建时不会保留逐元素类型约束。
  • 成本来源:runtime 类型校验成本来自对象遍历、属性查找、函数调用、annotation 求值和可能的用户代码执行。
  • 工程顺序:先识别 annotation 消费者,再定位 runtime 对象,随后确认数据来源,最后决定静态检查、边界校验和深度 introspection 的放置位置。