supervisor 中关于上下文多轮信息维护常用到的python语法

数据结构梳理

你问得关键:语法只是"怎么写",数据结构才是"数据长什么样、能做什么操作"。

你文件里反复出现的其实就 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 是 dict
  • existing 是 list
  • current_ids 是 set
  • current 里的元素可以是 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 是 list
  • current 是 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"]}

可以翻成人话:

  1. state.get("messages", []):安全取 messages,没有就给 []
  2. or []:如果取到 None 或空,也保证变成 []
  3. list(...):复制一份,别改原 state
  4. {getattr(m, "id", None) for m in current}:把 current 里每个对象的 id 收集成集合
  5. [m for m in existing if ... not in current_ids]:从 existing 里筛出 current 没有的
  6. [*missing, *current]:missing 在前,current 在后,拼成新列表
  7. getattr(m, "id", None):没有 id 属性也不报错,返回 None
  8. {**state, "messages": ...}:复制 state 的所有键,但把 messages 替换掉
相关推荐
青少儿编程课堂1 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C/C++考级:任意进制互转与大数多进制加法解析
c++·python·算法·bfs·信息学竞赛
曹牧1 小时前
C#:字符串占位符与值数量不一致
开发语言·数据库·c#
Orchestrator_me1 小时前
k8s网络与应用
网络·python·kubernetes
迅猛龙办公室1 小时前
Python函数实现反转一个只有3位数的整数
开发语言·前端·python
我星期八休息1 小时前
Redis 的原理和使用(2)
前端·数据库·redis·python·mysql·缓存·bootstrap
“AI国潮设计-小江”1 小时前
《【AIGC实战】Python+SDXL批量生成“英歌舞树莓香草布丁”IP,附保持角色一致性的代码逻辑》
开发语言·人工智能·python·prompt·aigc
闻缺陷则喜何志丹1 小时前
【C++特性及类库】多线程
开发语言·c++·多线程·语言特性·系统库
溪语流沙2 小时前
【Web全栈进阶】文件上传与图片展示:早报站的第一张图片
前端·vue.js·python·fastapi
川石课堂软件测试2 小时前
JMeter异步接口测试实战
数据库·python·jmeter·mysql·kubernetes·测试用例·prometheus