4.1 列表、元组与切片
目标
- 掌握 list 的增删改查、排序与复制,区分“原地修改”与“返回新对象”。
- 理解 tuple 的不可变性与解包(含
*rest)。 - 掌握切片
[start:stop:step]的完整规则。
核心概念
列表 list:有序、可变
messages = [{"role": "user", "content": "你好"}]
messages.append({"role": "assistant", "content": "你好!"}) # 末尾追加一个元素
messages.extend([{"role": "user", "content": "天气?"}]) # 追加多个元素
messages.insert(0, {"role": "system", "content": "简洁回答"}) # 指定位置插入
print(len(messages), [m["role"] for m in messages])
last = messages.pop() # 弹出并返回最后一个
print(last["content"], len(messages))
print(messages[0]["role"], messages[-1]["role"]) # 索引,负数从末尾数| 操作 | 原地修改,返回 None | 返回新对象 |
|---|---|---|
| 排序 | xs.sort() | sorted(xs) |
| 反转 | xs.reverse() | xs[::-1]、list(reversed(xs)) |
| 追加 | xs.append(x)、xs.extend(ys) | xs + ys |
nums = [3, 1, 2]
result = nums.sort()
print(result, nums) # None [1, 2, 3]:sort 原地修改,返回 None
print(sorted([3, 1, 2], reverse=True))
print(2 in nums, nums.index(2), nums.count(2))
print(sum(nums), min(nums), max(nums))复制:浅拷贝与深拷贝
b = a 不复制;a.copy()、list(a)、a[:] 是浅拷贝:新列表,但元素仍是同一批对象。嵌套结构需要 copy.deepcopy。
import copy
a = [{"n": 1}, {"n": 2}]
shallow = a.copy()
deep = copy.deepcopy(a)
a[0]["n"] = 99
print(shallow[0]["n"], deep[0]["n"]) # 99 1元组 tuple:有序、不可变
元组用逗号构造(括号常可省略),适合表示“固定结构的一组值”,例如函数的多个返回值、字典的键。
point = (3, 4)
single = (1,) # 单元素元组必须有逗号
also_tuple = 1, 2, 3
print(type(single), also_tuple)
x, y = point # 解包
first, *rest = [1, 2, 3, 4] # 星号收集剩余
*init, last = "abc"
print(x, y, first, rest, init, last)
for i, (name, score) in enumerate([("a", 90), ("b", 80)]): # 嵌套解包
print(i, name, score)切片
seq[start:stop:step] 适用于 list、tuple、str:包含 start、不包含 stop;省略 start 表示从头,省略 stop 表示到尾;step 为负时反向。切片总是返回新对象,越界不会报错。
xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(xs[2:5]) # [2, 3, 4]
print(xs[:3]) # 前 3 个
print(xs[-3:]) # 后 3 个
print(xs[::2]) # 每隔一个
print(xs[::-1]) # 反转
print(xs[8:100]) # 越界:[8, 9],不报错
history = xs[-4:] # 常见用法:只保留最近 N 条
print(history)对 list 还可以给切片赋值或删除切片:
xs = [0, 1, 2, 3, 4]
xs[1:3] = ["a", "b", "c"] # 替换一段(长度可以不同)
del xs[0]
print(xs)易错点
x = xs.sort()得到None;要新列表用sorted(xs)。append一个列表会把整个列表作为一个元素;要逐个追加用extend。[[0] * 3] * 2:外层复制的是同一个内层列表的引用;用推导式[[0] * 3 for _ in range(2)]。- 元组“不可变”是浅层的:元组里放的列表仍然可以修改内容。
- 索引越界
IndexError,但切片越界不报错。
xs = [1, 2]
xs.append([3, 4])
print(xs) # [1, 2, [3, 4]]
grid = [[0] * 3] * 2
grid[0][0] = 1
print(grid) # [[1, 0, 0], [1, 0, 0]]:两行是同一个对象messages = []
print(messages[-1]) # 空列表没有最后一个元素对照
| 操作 | Python | JS | Java |
|---|---|---|---|
| 字面量 | [1, 2] | [1, 2] | List.of(1, 2) / new ArrayList<>() |
| 追加 | xs.append(x) | xs.push(x) | xs.add(x) |
| 最后一个 | xs[-1] | xs.at(-1) | xs.get(xs.size() - 1) |
| 子序列 | xs[1:3] | xs.slice(1, 3) | xs.subList(1, 3)(视图) |
| 解构 | a, *rest = xs | const [a, ...rest] = xs | 无 |
| 不可变序列 | tuple | Object.freeze([...]) | List.of(...) |
读代码
messages = [{"role": "user", "content": "上海今天天气怎么样?"}]
...
messages.append({"role": "assistant", "content": resp.content})
...
messages.append({"role": "user", "content": results})messages 是“字典组成的列表”,原地追加:调用工具的轮次追加两条——模型回复(assistant)和工具结果(user);最后一轮只追加 assistant 回复。因为模型不保存上下文,每轮 create(messages=messages) 都把完整列表发过去。读码时可以画一张表追踪它的长度和每个元素的 role。
recent = history[-20:] # 只保留最近 20 条,控制上下文长度
role, content = msg["role"], msg["content"]练习
练习 1:保留最近 N 条
实现 trim_history(messages, n):返回最近 n 条消息组成的新列表;n <= 0 时返回空列表。不要修改原列表。
def trim_history(messages, n):
return messages
练习 2:追加一轮对话
实现 add_turn(messages, assistant_content, tool_results):原地向 messages 追加两条消息:{"role": "assistant", "content": assistant_content} 与 {"role": "user", "content": tool_results},返回 None。
def add_turn(messages, assistant_content, tool_results):
messages = messages + []
练习 3:解包
record = ("get_weather", {"city": "上海"}, "上海:晴")。用一行解包得到 name、args、output;再用星号解包从 turns = [1, 2, 3, 4, 5] 得到 first 和 others。
record = ("get_weather", {"city": "上海"}, "上海:晴")
turns = [1, 2, 3, 4, 5]
小结
- list 可变:
append/extend/pop/sort原地修改;sorted/切片返回新对象。 - 浅拷贝只复制最外层;嵌套结构用
copy.deepcopy。 - tuple 不可变,用于固定结构;解包支持嵌套和
*rest。 - 切片
[start:stop:step]含头不含尾、越界不报错。