02.02-Agent学习记录-类型系统与数据结构

上一篇用几段 Python 代码,把会议名称和开始时间组合成通知。继续写下去,很快会遇到一个问题:看起来差不多的数据,为什么有的能直接计算,有的却会报错?很多条会议记录,又应该怎样组织?

舒翱移动端 / 全栈 / Agent 应用

正文

这就需要认识类型系统与数据结构。

先看会议时长。数字 30 可以表示三十分钟,而带引号的“30”是一段文字。人阅读时可能知道它们想表达同一件事,程序却需要根据具体类型来决定支持什么操作。

类型说明一个值具有怎样的性质、能够进行哪些操作;类型系统则规定这些类型与操作之间的关系。

在 Python 中,每个对象都有类型。整数可以参与整数运算,字符串可以进行文字拼接,但字符串和整数不能不加说明地直接相加。需要转换时,应当明确按照什么规则转换。对象、值与类型

例如:

duration_minutes = 30
duration_text = "30"

print(duration_minutes + 5)
print(duration_text + "5")

第一项结果是数字 35,第二项结果是文字“305”。后者是在连接两段文字,并没有把它们当作分钟进行加法。如果写成 duration_text + 5,则会因不支持这样的类型组合而报错。

这说明,看到内容里有数字字符,并不能直接认定它已经是可计算的数字。从表单、文件或接口取得的数据,需要先弄清实际类型,再决定后续处理。

Python 常被称为动态类型语言。这里可以先理解为:变量名不必预先固定为某一种类型,它可以先关联一个整数,后来又关联一段文字;具体操作是否支持,会受到当时对象类型的影响。

例如,把 duration_minutes 从数字 30 改成字符串“待确认”,后面的加法就可能无法继续。动态类型带来书写上的灵活,也要求程序在数据边界上保持清楚的约定。

类型也不只涉及文字和整数。会议是否已经确认,可以使用布尔值 True 或 False;当前没有取得地点,可以用 None 表示某种约定的缺失状态。

None、空字符串、数字 0 和 False,在含义上不能随意互换。地点为空,可能表示尚未填写;会议时长为 0,可能表示一个确实收到、但需要进一步核对的数值。究竟允许哪一种情况,应由数据约定说明。

这也是为什么“只要不为空就继续”有时不够。它可能把有效的 0 当作缺失,也可能让一段非空但无效的时间文字通过。类型检查、内容检查与业务判断,需要分别设计。

带小数的数值还会遇到表示方式的问题。常用浮点数并不能精确表示所有十进制小数,例如在通常的 Python 浮点计算中,0.1 + 0.2 的结果不与 0.3 精确相等。这与二进制浮点表示有关,不能把它理解为语言随意算错了加法。浮点数的表示与误差

需要怎样比较数值、允许多大误差,应结合任务决定。对计数、近似测量和要求精确小数的处理,适合的数据表示可能不同,不应仅凭“它有小数点”就采用同一种方案。

认识了单个值,再看数据怎样组织。

一场会议通常有标题、时长、确认状态等不同字段。可以用字典把字段名称与相应值联系起来:

meeting = {
    "title": "项目例会",
    "duration_minutes": 30,
    "confirmed": False
}

print(meeting["title"])

这里的字典叫 dict。title、duration_minutes 和 confirmed 是键,各自关联相应的值。通过 meeting["title"],可以按字段名称取得会议标题。

字典适合表示“某个名称对应什么内容”。但键名要保持一致。程序约定读取 title,数据却只提供 name,就需要明确转换或报告问题;电脑不会自动知道这两个字段在业务上是否等价。

如果有多场会议,可以把这些会议字典放进列表。列表叫 list,适合保存有顺序的一组项目,可以按位置访问,也可以逐项遍历。

列表的第一个位置从索引 0 开始。顺序可以用来表示资料返回顺序或明确安排的处理顺序,但它不会自动变成按时间先后排序。需要怎样排列,仍然要写出规则。

还有一些常见组织方式。例如,元组 tuple 可以表示一个位置固定、整体不可修改的序列;集合 set 适合表达不重复的项目及成员关系。

如果想统计参加会议的不同人员,集合可以帮助排除重复项。如果要保留主持人规定的发言顺序,就不能把这种业务顺序交给集合表达。数据结构怎样组织内容,会直接影响哪些关系能够保留下来。列表、元组、集合与字典

这里说元组不可修改,是指它所保存的元素引用不能被重新安排;如果元素本身指向可变对象,那个对象仍可能改变。初学时先记住,“外层不可变”并不自动保证内部所有内容都不可变。

接下来还需要认识可变对象与引用。

看一段短代码:

attendees = ["小林"]
backup = attendees
backup.append("小周")
print(attendees)

这段代码会让 attendees 也表现出新增了“小周”。原因是 backup = attendees 没有另造一份名单,而是让两个名称都关联同一个列表对象。

因此,把变量名取成 backup,并不意味着已经获得独立备份。修改共享的可变对象,会影响其他仍在使用它的地方。

复制也需要说明层次。列表的浅复制会新建外层列表,但如果里面还有其他列表或字典,内部对象仍可能被共享。是否需要更深的复制,取决于数据结构和后续操作,不能看到 copy 这个名字就假定完全隔离。对象身份与可变性

在 Agent 应用中,这种问题可能表现为:审核步骤修改了一份草稿记录,原始记录却也跟着变了。维护清楚的状态,需要知道是在生成新数据,还是在原对象上修改。

除了值的运行时类型,程序还可以写类型标注,说明期望接收和返回什么。例如:

def extend_duration(minutes: int) -> int:
    return minutes + 5

这里的 int 表示函数期望使用整数,并返回整数。类型标注有助于阅读,也能供相应工具在运行前发现不匹配的调用。

但 Python 运行时不会仅因为写了这项标注,就自动验证所有输入。若传入字符串后加法报错,原因来自实际运算不支持这个组合,而不是标注自动拦截了调用。类型标注与运行时检查的边界

因此,类型标注、类型检查工具和实际数据校验,是可以配合使用的不同机制。外部传入一份数据,仍需要检查字段、类型、允许范围,以及当前业务是否接受它。

类型正确也不等于事实正确。一个整数 30 符合“时长为整数”的要求,却仍可能与实际会议时长不一致。一个标题字符串格式正常,也不证明找到了用户想要的会议。

同样,字典只是程序中的数据组织方式,不必然就是后面要学习的 JSON 文本。数据从内存进入文件或网络时,需要一种明确的表示和转换约定。

在日历 Agent 中,查询结果可以被整理成会议记录,记录中包含有明确含义的字段,多个记录放入列表,当前任务状态再保留已经选中的会议。模型、工具和普通程序之间,因而能够交换结构较清楚的信息。

初学时,可以先检查几件具体的事:这个值是什么类型,这组数据有哪些字段,是否需要保留顺序,能否重复,修改后会不会影响其他使用者。选对组织方式,后面的处理通常更容易说清。

下一篇进入 02.03:算法复杂度与资源开销,继续看数据规模增大以后,相同任务的不同处理方式,为什么会消耗不同的时间与内存。


当前学习编号:02.02 类型系统与数据结构。
下一学习编号:02.03 算法复杂度与资源开销。后续还有其他学习编号。

关联分支:外部数据处理对应 02.05 JSON 与序列化、02.06 Schema 与数据校验、05.03 数据格式与字段规范、16.03 工具输入 Schema;组织与维护对应 02.03 算法复杂度与资源开销、19.02 工作状态与业务状态、21.18 文件格式与模式演进。

动态类型、静态类型检查、类型标注、可变与不可变对象、对象引用、浅复制与深复制、列表、元组、集合和字典,是本编号可继续下钻的支系知识。

系列阅读

本篇学习编号:02.02 类型系统与数据结构。

上一篇: 02.01 Python 编程 。

下一篇: 02.03 算法复杂度与资源开销 。

延伸阅读

返回全部文章

搜索项目和文章

输入关键词,查找项目或文章。

使用 Tab 选择结果,按 Esc 关闭。