数据结构梳理
你问得关键:语法只是"怎么写",数据结构才是"数据长什么样、能做什么操作"。
你文件里反复出现的其实就 5 样东西:
| 数据结构 | 长什么样 | 生活比喻 | 在你代码里的角色 |
|---|---|---|---|
dict 字典 |
{"messages": [...], "tools": [...]} |
带标签的抽屉 | state 存不同字段 |
list 列表 |
["m1", "m2", "m3"] |
排队的人 / 购物清单 | messages 保持顺序、可重复 |
set 集合 |
{"h1", "h2"} |
签到表 / 身份证号集合 | current_ids 快速判断"在不在" |
| 对象 object | m.id |
贴了属性标签的盒子 | 消息对象 m,用 getattr 取 id |
None |
None |
空 / 没有 | 默认值、空值兜底 |
下面逐个讲。
1. dict 字典:带标签的抽屉
python
state = {
"messages": ["m1", "m2"],
"tools": ["搜索工具"],
"user": "小明"
}
dict 是"键 → 值"的映射:
python
state["messages"] # 取 messages 这个抽屉里的东西
但直接取不存在的键会报错:
python
state["abc"] # KeyError
所以用 .get():
python
state.get("messages", []) # 有就取,没有给 []
state.get("abc", []) # 没有 abc,返回 []
比喻:
字典像一排抽屉,每个抽屉外面贴着标签 "messages"、"tools"。
.get("tools", []) 就是:"有 tools 抽屉就打开;没有就给我一个空列表,别炸。"
你代码里的:
python
return {**state, "messages": new_messages, "tools": unique_tools}
就是:
把
state这个字典里的所有抽屉先复制过来,然后把"messages"和"tools"两个抽屉换成新的。
2. list 列表:有顺序、可重复的排队
python
messages = ["m1", "m2", "m3"]
列表特点:
- 有顺序:第 0 个、第 1 个......
- 可以重复:
["m1", "m1"]没问题 - 可以追加:
messages.append("m4") - 可以索引:
messages[0]
python
messages = ["m1", "m2"]
messages.append("m3")
print(messages) # ["m1", "m2", "m3"]
比喻:
列表像排队的人,谁先谁后有讲究,同一个人理论上也可以排两次。
你代码里的 messages 就是列表,因为消息顺序很重要。
重点:list(x) 是复制,= 不是复制
python
a = ["m1", "m2"]
b = a # b 和 a 是同一个列表的两个名字
b.append("m3")
print(a) # ["m1", "m2", "m3"]
python
a = ["m1", "m2"]
b = list(a) # 新建一个列表,内容复制一份
b.append("m3")
print(a) # ["m1", "m2"]
print(b) # ["m1", "m2", "m3"]
比喻:
b = a:两个人共用一个购物清单,一个人改,另一个也看到变化。b = list(a):复印了一份购物清单,各改各的。
你代码里:
python
existing = list(state.get("messages", []) or [])
就是"安全取出来,再复印一份",后面改 existing 不会改到原始 state。
3. set 集合:不重复、查得快
python
ids = {"h1", "h2", "h1"}
print(ids) # {"h1", "h2"},自动去重
集合特点:
- 不重复
- 没有顺序概念
- 判断
x in 集合很快 - 适合"去重"和"查在不在"
python
current_ids = {"h1", "h3"}
print("h1" in current_ids) # True
print("h2" in current_ids) # False
比喻:
集合像签到表:一个人签一次就够了;你问"张三签到了吗",扫一眼名字集合很快。
列表像排队名单,你问"张三在不在",可能要从头一个个比。
你代码里:
python
current_ids = {getattr(m, "id", None) for m in current}
意思是:
把
current里每个消息的id收集起来,放进一个集合,自动去重。
然后:
python
missing = [m for m in existing if getattr(m, "id", None) not in current_ids]
就是:
从
existing里挑出那些 id 不在current_ids集合里的消息。
为什么用 set?
因为要反复判断"这个 id 在不在 current 里",集合查得快。
4. 对象 object:带属性的盒子
python
class Message:
def __init__(self, id):
self.id = id
m = Message("h1")
print(m.id) # h1
对象可以理解成"一个盒子,上面贴着属性标签":
m.id:盒子的 id 标签m.content:盒子的 content 标签
但有时候传进来的不是对象,而是字典:
python
m = {"id": "h1"}
print(m.id) # 报错,字典没有 .id
所以用:
python
getattr(m, "id", None)
意思是:
如果
m有id属性,就取出来;没有就返回None,别报错。
比喻:
你收到一个包裹,可能贴着 id 标签,也可能没贴。
getattr(m, "id", None) 就是:"有 id 标签就读出来;没有就当作没有,返回 None。"
5. None:表示"没有"
python
x = None
print(x is None) # True
None 不是 0,不是 "",不是 []。它表示"空 / 没有值"。
你代码里:
python
getattr(m, "id", None)
如果 m 没有 id,就得到 None。
而:
python
state.get("messages", []) or []
如果取到 None,None or [] 会变成 []。
python
None or [] # []
[] or [] # []
["m1"] or [] # ["m1"]
记忆: or [] 是"空值兜底",保证最后一定是个列表。
6. 解包 * 和 **:把内容拆开铺进去
* 用于列表
python
missing = [1, 2]
current = [3]
merged = [*missing, *current]
print(merged) # [1, 2, 3]
比喻:
missing 是一叠牌,*missing 就是把牌一张张摊开;
[*missing, *current] 就是"missing 的牌先摊,current 的牌后摊,组成新牌堆"。
** 用于字典
python
state = {"messages": ["旧"], "user": "小明"}
new = {**state, "messages": ["新"]}
print(new)
# {"messages": ["新"], "user": "小明"}
比喻:
**state 就是把字典里所有"标签 → 值"都复制进来;
后面再写 "messages": ["新"],同名标签会被覆盖。
7. 把这几个数据结构串起来看
假设:
python
state = {
"messages": ["旧消息1", "旧消息2"]
}
current = [
{"id": "h2"},
{"id": "h3"}
]
你代码类似:
python
existing = list(state.get("messages", []) or [])
current_ids = {getattr(m, "id", None) for m in current}
得到:
python
existing = ["旧消息1", "旧消息2"]
current_ids = {"h2", "h3"}
这里:
state是 dictexisting是 listcurrent_ids是 setcurrent里的元素可以是 dict 或对象getattr(..., None)处理"没有 id"的情况
然后:
python
missing = [m for m in existing if getattr(m, "id", None) not in current_ids]
如果 existing 里的元素也有 id,就会筛出"current 里没有的"。
最后:
python
merged["messages"] = [*missing, *current]
missing是 listcurrent是 list[*missing, *current]得到新的 list
最后一张表帮你记
| 你看到 | 数据结构 | 一句话 |
|---|---|---|
{"messages": [...]} |
dict |
标签找值,.get 安全取 |
["m1", "m2"] |
list |
有顺序、可重复、可 append |
{"h1", "h2"} |
set |
不重复、查 in 快 |
m.id |
对象属性 | 盒子上的标签 |
None |
空值 | 表示"没有" |
list(x) |
复制列表 | 新建一份,别改原数据 |
[*a, *b] |
列表解包拼接 | a 在前,b 在后 |
{**d, "k": v} |
字典解包覆盖 | 复制 d,替换 k |
最核心一句:
state 是字典,messages 是列表,current_ids 是集合;列表保顺序,集合查重快,字典按标签取,None 表示没有,*/** 是拆开铺进去。
① dict.get(key, 默认值)
python
state = {"messages": ["m1", "m2"]}
print(state.get("messages", [])) # 有 key,取到 ["m1", "m2"]
print(state.get("tools", [])) # 没 key,返回默认值 []
结果:
python
["m1", "m2"]
[]
对比:
python
state["tools"] # 报错 KeyError,因为 tools 不存在
记忆点: .get() 是安全取值,没有这个 key 也不炸,给你默认值。
② or [] 空值兜底
python
a = None
b = []
c = ["m1"]
print(a or []) # None 是假,用右边 []
print(b or []) # [] 是假,用右边 []
print(c or []) # ["m1"] 是真,用它自己
结果:
python
[]
[]
["m1"]
记忆点: x or [] 的意思是:
"如果 x 是 None、空列表、空字符串、0 等假值,就换成 [];否则用 x。"
③ list(...) 复制一份
python
original = ["m1", "m2"]
copy_list = list(original)
copy_list.append("m3")
print(original) # ["m1", "m2"]
print(copy_list) # ["m1", "m2", "m3"]
如果直接赋值:
python
original = ["m1", "m2"]
same_list = original
same_list.append("m3")
print(original) # ["m1", "m2", "m3"]
记忆点: list(x) 会新建一个列表。
same_list = original 只是两个名字指向同一个列表,改一个另一个也变。
④ [*a, *b] 列表解包 / 拼接
python
missing = [1, 2]
current = [3]
merged = [*missing, *current]
print(merged) # [1, 2, 3]
等价于:
python
merged = missing + current
再比如:
python
a = ["旧1", "旧2"]
b = ["新1"]
print([*a, *b]) # ["旧1", "旧2", "新1"]
记忆点: *a 就是把 a 里面的元素"拆开铺在这里"。
[*a, *b] = a 的元素在前,b 的元素在后。
⑤ {x for x in ...} 集合推导式
python
messages = [
{"id": "h1"},
{"id": "h2"},
{"id": "h1"},
]
ids = {m["id"] for m in messages}
print(ids) # {"h1", "h2"}
注意:h1 出现了两次,但集合会自动去重。
等价写法:
python
ids = set()
for m in messages:
ids.add(m["id"])
查某个 id 在不在:
python
print("h1" in ids) # True
记忆点: 集合推导式得到的是 set,大括号 {},自动去重,查 in 很快。
⑥ [m for m in ... if 条件] 带过滤的列表推导式
python
existing = ["m1", "m2", "m3"]
current_ids = {"m1", "m3"}
missing = [m for m in existing if m not in current_ids]
print(missing) # ["m2"]
读法:
对
existing里的每个m,只有m not in current_ids时才留下。
等价写法:
python
missing = []
for m in existing:
if m not in current_ids:
missing.append(m)
多条件例子:
python
nums = [1, 2, 3, 4, 5]
result = [
n for n in nums
if n > 2 and n % 2 == 1
]
print(result) # [3, 5]
记忆点: [留下什么 for 每个元素 in 原列表 if 条件]。
条件为 True 才留下。
⑦ getattr(obj, "属性名", 默认值)
python
class Message:
def __init__(self, id):
self.id = id
m1 = Message("h1")
m2 = object() # 没有 id 属性
print(getattr(m1, "id", None)) # h1
print(getattr(m2, "id", None)) # None
如果直接写 m2.id:
python
print(m2.id) # 报错 AttributeError
记忆点: getattr(m, "id", None) 意思是:
"有 m.id 就取出来;没有就返回 None,不要报错。"
它适合处理"不确定对象有没有这个属性"的情况。
⑧ **state 字典解包 + isinstance
8.1 **state 字典解包
python
state = {
"messages": ["旧消息"],
"tools": ["旧工具"],
"user": "小明",
}
new_messages = ["新消息"]
unique_tools = ["新工具"]
result = {
**state,
"messages": new_messages,
"tools": unique_tools,
}
print(result)
结果:
python
{
"messages": ["新消息"],
"tools": ["新工具"],
"user": "小明"
}
理解顺序:
python
{
**state, # 先把 state 所有键值放进来
"messages": new_messages, # 如果 messages 已存在,覆盖它
"tools": unique_tools # 如果 tools 已存在,覆盖它
}
记忆点: {**state, "messages": 新值} = 以 state 为底,替换掉指定 key。
8.2 isinstance
python
class ToolMessage:
pass
class NormalMessage:
pass
a = ToolMessage()
b = NormalMessage()
print(isinstance(a, ToolMessage)) # True
print(isinstance(b, ToolMessage)) # False
再比如:
python
print(isinstance(123, int)) # True
print(isinstance("abc", int)) # False
记忆点: isinstance(x, 类型) 判断"x 是不是这个类型"。
你文件里的:
python
isinstance(msg, ToolMessage)
就是判断 msg 是不是 ToolMessage 这类对象。
最后用一句话串起来
你文件里那行典型代码:
python
existing = list(state.get("messages", []) or [])
current_ids = {getattr(m, "id", None) for m in current}
missing = [m for m in existing if getattr(m, "id", None) not in current_ids]
merged["messages"] = [*missing, *current]
return {**state, "messages": merged["messages"]}
可以翻成人话:
state.get("messages", []):安全取 messages,没有就给[]or []:如果取到None或空,也保证变成[]list(...):复制一份,别改原 state{getattr(m, "id", None) for m in current}:把 current 里每个对象的 id 收集成集合[m for m in existing if ... not in current_ids]:从 existing 里筛出 current 没有的[*missing, *current]:missing 在前,current 在后,拼成新列表getattr(m, "id", None):没有 id 属性也不报错,返回 None{**state, "messages": ...}:复制 state 的所有键,但把 messages 替换掉