正文
这就需要认识类型系统与数据结构。
先看会议时长。数字 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 文件格式与模式演进。
动态类型、静态类型检查、类型标注、可变与不可变对象、对象引用、浅复制与深复制、列表、元组、集合和字典,是本编号可继续下钻的支系知识。