关于copy() 浅拷贝
假设我们有一个List[]列表的数据类型,我们再方法返回时直接返回数据对象本身(例如构建大模型消息类型时的history)
class Agent:
def __init__(self):
self._history = ["消息1", "消息2"]
def get_history(self):
return self._history.copy()若是直接返回,外部可以对数据进行修改,包括数据的结构,以及数据内容。 直接返回是直接把容器的地址返回给调用者,所以调用者可以对容器的内容进行随意修改,任何操作都可以。
若是使用copy()进行返回
class Agent:
def __init__(self):
self._history = ["消息1", "消息2"]
def get_history(self):
return self._history.copy()这里调用copy()之后,返回的是另一个地址空间,该地址中保存了原本容器中的所有元素的原始地址,这样做的好处如下:
- 对于容器清空、插入新数据、添加数据这类操作都不会影响原始数据,因为拿到的是新创建的容器地址,容器中存的只是原始元素的地址,上述操作都是再新容器地址空间中进行操作的,原始地址空间中的内容不会发生变化。
- 对于直接修改已有的数据的内容可以实现,因为新容器中使用的是原始元素的地址,指向的是同一个位置,所以修改后也是直接修改的原容器中的数据。
- 实现了对数据的保护,用户不能随意的篡改结构方面的内容,不过仍能修改信息,但是对于大模型返回的消息一般都是查看,使用深拷贝太过于耗时复杂,使用浅拷贝在提升安全性的同时,性能又好,所以使用浅拷贝返回大模型的会话历史这类信息。
关于inspect.getmembers方法
- inspect.getmembers(self, ...):这是 Python 内置 inspect 模块提供的反射函数。它会遍历当前实例(self)的所有属性(包括方法、成员变量等)
- predicate=inspect.ismethod:这是一个过滤条件(断言)。它告诉 getmembers:我只要那些是“实例方法”的属性,过滤掉普通字段、类方法、静态方法或属性(property)
- inspect.ismethod 筛选的是实例方法(类中的普通方法,非静态、类方法)。简单说,它抓取的是定义时第一个参数必须写 self 的那些函数
关于hasattr()方法
检查某个对象身上是否存在某个指定的“属性”或“方法名”。存在返回 True,不存在返回 False
- 语法:hasattr(对象, "属性名字符串")
if hasattr(method, '_is_tool_action'):for name,method in xxx方法
从目标中提取指定属性的内容
for name,method in inspect.getmembers(self, predicate=inspect.ismethod)这里inspect.getmember返回的是一个列表,列表中每一个属性都是一个元组:固定格式是 (属性名, 属性值)。这里只过滤得到实例方法,所以是(方法名,方法本体)
- 假设当前实例 self 有一个方法叫 _add_memory。
- inspect.getmembers 就会生成一个元组:('_add_memory', '方法本体')
for 循环的拆包(解构):
- Python 的 for name, method in ... 语法,就是自动把元组里的两个值分别拿出来。
- name:接收元组里的第一个值,它是一个字符串(比如 "_add_memory"),代表属性的名字。
- method:接收元组里的第二个值,它就是该方法对象本身(可以直接被调用),代表属性的值。
简单类比:
如果有一本通讯录,记录了 [("张三", "13800138000"), ("李四", "13900139000")]。 当你写 for name, phone in 通讯录: 时:
name 就是提取出来的 "张三"(名字)。
phone 就是提取出来的 "13800138000"(号码)。
列表推导式和生成器表达式
语法都一致,都是生成得值 for循环 过滤条件,不同的点是:
- 列表推导式是先生成一个列表再一下返回,并且是被
[]包裹、 - 生成器表达式是边生成边返回,被
()包裹
method.__name__.lstrip('_')方法
- method.name:这是 Python 函数/方法的内置属性,用来获取方法名的字符串。比如你定义 def _add_memory(self):,那么 method.name 的值就是 "_add_memory"。
- .lstrip(''):这是 Python 字符串的方法,只去除字符串开头(左侧)的指定字符。这里传了 '',意思就是把开头的下划线全部切掉。
提取方法注释内容生成方法名给大模型
# 从方法的文档字符串(docstring)里提取第一句作为工具的描述
def _extract_description_from_docstring(self) -> str:
"""从 docstring 提取描述"""
doc = inspect.getdoc(self.method)
if not doc:
return f"执行 {self.method.__name__}"
# 提取第一行作为描述
lines = doc.split('\n')
for line in lines:
line = line.strip()
if line and not line.startswith('Args:') and not line.startswith('Returns:'):
return line
return f"执行 {self.method.__name__}"doc = inspect.getdoc(self.method)- inspect.getdoc 会帮你获取方法的 doc 属性,并自动处理缩进(把缩进统一去掉),如果没写文档字符串,就返回 None。
if not doc: return f"执行 {self.method.__name__}"- 如果没写文档,就不硬去解析了,直接返回一个朴素的默认描述:“执行 xxx”。
lines = doc.split('\n')- 如果有文档,按换行符拆分成多行。
for line in lines: 遍历,并执行 line.strip()- 把每一行的首尾空白字符(空格、制表符)去掉,得到干净的内容。
if line and not line.startswith('Args:') and not line.startswith('Returns:'):这个条件是筛选规则:
line:排除空行。not line.startswith('Args:'):排除以 Args: 开头的行(参数说明部分)。not line.startswith('Returns:'):排除以 Returns: 开头的行(返回值说明部分)。
这样过滤之后,剩下的通常就是文档字符串的第一句摘要。
找到第一个符合条件的行,立即
return line。- 如果遍历完所有行都没找到符合条件的(比如文档只写了 Args: 没写摘要),就返回默认描述
示例如下
"""添加一条新的记忆
Args:
content: 记忆的内容
Returns:
操作结果字符串
"""第一行 "添加一条新的记忆" → 去掉首尾空格后,不是空行,不以 Args: 或 Returns: 开头 → 立即返回 "添加一条新的记忆"
关于方法签名的内容
官方定义:方法签名(Signature)是 Python 中用于描述“如何调用这个方法”的元数据。它只关注输入(参数)和输出(返回值)的结构定义。
具体包含什么:
- 参数的名字(如 content, importance)
- 参数的类型注解(如 : str, : float)
- 参数的默认值(如 = 0.5)
- 返回值的类型注解(如 -> str)
- 绝对不包含什么:它不包含方法的文档字符串("""文档内容..."""),也不包含方法的名字(
__name__是单独的属性)。
假设你的方法是 def _add_memory(self, content: str, importance: float = 0.5) -> str:,打印出来的签名对象(Signature)显示为:
(self, content: str, importance: float = 0.5) -> str
在代码里,它是一个 inspect.Signature 对象。遍历它的 .parameters 属性,你会得到一个个 inspect.Parameter 对象,每个对象都有 .name(名字)、.default(默认值)、.annotation(类型注解)等属性。