迁移基础 · 运行与表达 · LESSON 02

值、类型与集合:同一个需求的三种表达

把对象、数组和类型系统的经验迁移到 list、vector、map 和 struct。

10 分钟types · objects · arrays · collections

先区分“形状”和“容器”

TypeScript 的 interfacetype 主要描述值的形状,JavaScript 的对象仍然可以在运行时增加字段。Python 的 dict 是通用映射,读取不存在的键会在运行时失败;C++ 的 struct 更像一份编译期可检查的字段契约。迁移一个对象列表时,不要只做语法替换,要先判断数据是临时中间值、外部消息,还是会被多个模块长期消费的模型。

这个判断直接影响错误出现的位置。探索性 Python 脚本可以用字典快速验证想法,模型输入和机器人消息则需要明确单位、缺失值、范围和所有权。C++ 的 std::vector 保存连续元素,复制和移动有成本;Python/JS 的集合保存对象引用,表面上的赋值往往不是深拷贝。相同的业务需求,在三个运行时里可能拥有不同的性能与别名风险。

TRANSLATION LENS 同一个意图,两种工程表达 窄屏可左右滑动查看完整代码
JS / TS
type Reading = { value: number; unit: string };
const readings: Reading[] = [{ value: 23, unit: "°C" }];
Python
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 经验如何迁移

数组的 mapfilter 很适合表达“生成新集合”,但不要默认 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 把巨型数据反复转换。

迁移练习

01
TRY IT YOURSELF

把一个对象列表变成可检查的数据

写出一个 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++ 时,先说清值的用途和所有权,再选语言类型。

当前学习阶段运行与表达
0/7

阶段共 7 节课,按顺序完成更容易建立完整的迁移模型。