值、类型与集合:同一个需求的三种表达
把对象、数组和类型系统的经验迁移到 list、vector、map 和 struct。
先区分“形状”和“容器”
TypeScript 的 interface 或 type 主要描述值的形状,JavaScript 的对象仍然可以在运行时增加字段。Python 的 dict 是通用映射,读取不存在的键会在运行时失败;C++ 的 struct 更像一份编译期可检查的字段契约。迁移一个对象列表时,不要只做语法替换,要先判断数据是临时中间值、外部消息,还是会被多个模块长期消费的模型。
这个判断直接影响错误出现的位置。探索性 Python 脚本可以用字典快速验证想法,模型输入和机器人消息则需要明确单位、缺失值、范围和所有权。C++ 的 std::vector 保存连续元素,复制和移动有成本;Python/JS 的集合保存对象引用,表面上的赋值往往不是深拷贝。相同的业务需求,在三个运行时里可能拥有不同的性能与别名风险。
type Reading = { value: number; unit: string };
const readings: Reading[] = [{ value: 23, unit: "°C" }]; readings = [{"value": 23, "unit": "°C"}]
reading = readings[0] 学习目标
- 能区分对象形状、通用容器和领域模型,知道类型声明何时只在编译期有效。
- 能在外部数据边界校验键、元素类型、数值有限性和集合大小。
- 能为临时值与跨模块数据分别选择轻量对象或显式模型,并说明复制/借用关系。
形状、容器和所有权
把 Reading[] 迁成 Python 时,可以先用 list[Reading] 表示集合,再决定 Reading 是字典、dataclass 还是验证模型;迁成 C++ 时,std::vector<Reading> 说明元素类型固定,但仍需决定函数接收值、const& 还是移动值。跨模块接口尽量接收“谁拥有这份数据”清楚的类型,而不是传一个可以任意改写的万能对象。
from dataclasses import dataclass
@dataclass(frozen=True)
class Reading:
value: float
unit: str
def positive_values(readings: list[Reading]) -> list[float]:
return [item.value for item in readings if item.value >= 0]
JS/TS 经验如何迁移
数组的 map、filter 很适合表达“生成新集合”,但不要默认 Python 推导式或 C++ 算法也会自动复制全部对象。对大批量图像元数据或点云,先估算元素数量和单个元素大小,再选择批量处理、视图或移动语义。对于从 JSON 进来的数据,类型标注只能帮助编辑器,必须在入口检查 typeof、键存在性、数值有限性和单位。
常见错误与排错思路
最常见错误是把 items = readings 当成拷贝,然后在函数内原地删改,导致调用方状态变化。排错时先打印对象身份/地址、集合长度和修改前后的关键字段;再写一个输入为空、重复引用和非法键的测试。C++ 若出现悬空引用,检查返回局部变量引用或容器扩容后保存的元素地址;Python 若出现 KeyError,回到反序列化边界补校验,不要在每个调用点散落默认值。
从松散对象列表构造可信集合
一个数组只保证“有若干元素”,并不保证每个元素都有正确字段。先校验输入形状,再生成内部使用的值;遇到错误要报告索引和字段,不能悄悄丢弃异常元素或把字符串强转成数字。
function parseReadings(raw) {
if (!Array.isArray(raw)) return { ok: false, code: "expected_array" };
const readings = [];
for (const [index, item] of raw.entries()) {
if (!Number.isFinite(item?.value)) return { ok: false, code: "invalid_value", index };
readings.push({ value: item.value });
}
return { ok: true, readings };
}
console.log(parseReadings([{ value: 2 }, { value: "3" }]));
预期第二个元素以 index: 1 报错。空数组可以是合法输入,但要由业务规则决定;不要把空、缺失和解析失败自动合并。Python 可用类型提示和 dataclass 表达已校验集合,C++ 可用 std::vector<Reading>,但边界检查仍不可省略。
运行验证:选择形状还是模型
仅在一个函数内使用的临时数据可保留为轻量对象;跨文件、进程或语言传递的结构要明确字段和版本;承载业务规则与不变量的实体则应通过受控构造避免非法状态。检查对象是否会被多个调用方持有,决定需要复制、只读视图还是共享所有权。
验证集合时覆盖空输入、缺字段、数值字符串、NaN、超大数组和重复引用。对图像/张量等大元素,先估算总字节数,再决定是否复制、流式消费或用类型化数组;不要为了统一 API 把巨型数据反复转换。
迁移练习
把一个对象列表变成可检查的数据
写出一个 Python 版本,从 readings 中取出所有 value 并过滤掉小于 0 的值;然后说明如何把同一规则表达成 C++ 的 std::vector 循环。至少为缺少 value、字符串数值和空集合各写一个预期结果。
给我一点提示
列表推导式可以同时完成遍历和条件过滤;真实项目中先把 dict 转成已验证的 Reading。
查看参考答案
Python 可先校验并构造 Reading,再写 values = [item.value for item in readings if item.value >= 0];C++ 用 const auto& 遍历 vector,满足条件时 push_back。缺少 value 或非数值输入应在边界返回带字段名的错误。 本节结论
集合语法只是表面。真正要迁移的是对数据边界的判断:临时脚本追求表达力,工程模块还要追求契约、校验和可观察性;当输入规模增长时,再用测量结果决定复制还是移动。
小结
对象形状是起点,可信内部集合需要验证,长期契约需要版本与单位,不变量模型还要控制构造。迁移到 Python 或 C++ 时,先说清值的用途和所有权,再选语言类型。
阶段共 7 节课,按顺序完成更容易建立完整的迁移模型。