Python:字符串伪装列表、字符串序列化对象解析踩坑实战

前 言

在做 AI 应用开发时,我们经常会拿到各种 “看起来是对象,实际是字符串” 的数据:

  1. 配置、接口返回:'["qwen-turbo","qwen-plus","deepseek"]',JSON 格式的字符串列表;
  2. 日志打印 / 缓存字符串:把List[Document]直接转字符串,得到一长串"[Document(metadata={...}, page_content='...'), ...]"

新手容易踩坑:直接eval()、直接split()暴力切割,要么有安全漏洞,要么解析不稳定。下面两个案例由浅入深分析。


案例一:JSON 格式字符串转 List

输入字符串:

raw_str ='["qwen-turbo","qwen-plus","deepseek"]'

这个字符串是标准 JSON 数组字符串。

❌ 错误写法 1:直接 split 分割

# 不推荐!如果字符串内部包含逗号、引号直接崩掉
res = raw_str.strip('[]').split(',')

问题:一旦元素内部出现逗号、转义引号,分割直接出错,鲁棒性极差。

❌ 错误写法 2:eval 执行

res = eval(raw_str)

虽然这里可以跑通,eval 极度危险:如果字符串来源是用户输入,传入恶意代码,会直接执行任意系统命令,生产环境严禁使用。

✅ 标准方案:使用json.loads()

import json

raw_str ='["qwen-turbo","qwen-plus","deepseek"]'
model_list: list = json.loads(raw_str)
print(model_list)
# ['qwen-turbo', 'qwen-plus', 'deepseek']
print(type(model_list)) # <class 'list'>

💡知识点:

  • json.loads()安全的 JSON 字符串反序列化,只解析 JSON 数据,不会执行代码;
  • json.dumps():对象转为 JSON 字符串。

边界提醒:JSON 字符串必须是双引号,单引号的字符串 json 库会报错。如果遇到单引号 JSON,需要特殊处理,不要直接丢给 eval。


案例二:字符串化的 LangChain Document 对象,提取 page

问题背景: 当你执行str([Document(...), Document(...)]),就会得到题目中的这一大段字符串。 注意:这不是 JSON!这是 Python 对象打印输出的文本表示

"[Document(metadata={'pk': 12, 'page': 2}, page_content='xxx'), Document(...)]"

我们的目标:提取每一个 Document 里面metadata["page"]页码,并且拼接page_content文本。

❌ 为什么不能用 json.loads?

  1. 里面是Document(...),这是 Python 类实例,JSON 没有 Document 类型;
  2. 字典内部使用单引号{'pk':12},JSON 规范强制双引号; 直接json.loads()直接抛出异常,完全无法解析。

⚠️ 不推荐方案:eval

# 仅本地调试可以看效果,生产绝对禁止!
doc_str = """[Document(metadata={'pk': 12, 'page': 2}, page_content='xxx'), ...]"""
docs = eval(doc_str)
  • 优点:一行代码直接还原List[Document]对象;
  • 致命风险:如果字符串来自外部输入,攻击者可以注入恶意 Python 代码,服务器直接沦陷。

适用场景:仅限本地调试、自己生成的日志字符串;接口、用户输入绝对禁用 eval

✅ 方案 A:安全解析 —— pyparsing /ast.literal_eval(推荐)

ast.literal_eval 是安全版本的 eval,只解析 Python 字面量:列表、字典、对象调用形式,不会执行函数和恶意代码。 但是注意:ast.literal_eval可以解析DictList,但是不能直接实例化 Document 类,遇到Document(...)会报错。

所以分两条路线:

  1. 如果你手上有 Document 类(已经导入 langchain 的 Document),可以结合 ast 做有限解析;
  2. 如果只有纯字符串文本,没有 Document 类,我们做正则提取,只拿我们关心的pagepage_content
方案 A‑1:正则提取(只拿数据,不需要还原 Document 对象,最稳妥)

不需要导入 langchain,不实例化对象,正则把page数字、page_content内容提取出来。

import re

doc_str = """[Document(metadata={'pk': 12, 'page': 2}, page_content='2. 费⽤报销:...'), Document(metadata={'pk': 27, 'page': 2}, page_content='2. 费⽤报销:...'), Document(metadata={'pk': 14, 'page': 3}, page_content='3. 最后警告:...')]"""

# 正则匹配 metadata={'xxx','page': N},提取page数字
page_pattern = re.compile(r"metadata=\{.*?'page':\s*(\d+)")
# 提取page_content的文本
content_pattern = re.compile(r"page_content='(.*?)'\)", re.S)

pages = page_pattern.findall(doc_str)
contents = content_pattern.findall(doc_str)

# pages是字符串数字,转int
page_nums = [int(p) for p in pages]
full_text = "\n".join(contents)

print("提取到页码列表:", page_nums)
print("拼接后的全部文档文本:\n", full_text)
  • 优点:零依赖、安全,不怕恶意输入,不需要导入 Document 类
  • 缺点:字符串格式如果发生变化(空格、换行)需要微调正则;适合日志、缓存字符串解析场景。
方案 A‑2:受控 eval /ast.literal_eval + Document 类(仅限可信内部数据源)

⚠️ 前提:字符串来源是程序自己打印输出的日志,不是用户输入

from langchain_core.documents import Document
import ast

raw_s = """[Document(metadata={'pk': 12, 'page': 2}, page_content='demo'),Document(metadata={'pk': 14, 'page':3}, page_content='demo2')]"""

# ast.literal_eval 无法直接处理Document(...),所以eval在这里,仅限可信数据源
docs = eval(raw_s)

page_list = [doc.metadata["page"] for doc in docs]
all_text = "\n".join([doc.page_content for doc in docs])

print(page_list)
print(all_text)

💡重要提醒:

ast.literal_eval不支持类实例化表达式Document(),所以这种打印出来的对象字符串,ast.literal_eval会抛异常,这种场景下 ast 不能替代 eval。 记住分界线:只要文本来自外部用户输入,坚决不能 eval

✅ 最佳工程实践:不要把str(list[Document])当做持久化存储

很多人踩坑根源:把str(docs)直接存数据库、缓存。这是错误做法! LangChain 原生提供序列化方法:

from langchain_core.documents import Document

docs: list[Document] = [...]
# 对象转可序列化字典列表
dict_list = [doc.model_dump() for doc in docs]

# 保存为json字符串
import json
json_str = json.dumps(dict_list, ensure_ascii=False)

# 还原回来
loaded_dicts = json.loads(json_str)
restore_docs = [Document(**item) for item in loaded_dicts]

✅ 存的时候转标准 JSON,读取的时候再重建 Document 对象,从根源避免这种诡异的 “对象打印字符串”。

这才是生产环境标准做法,不要把对象 print 出来的文本拿来解析。


核心总结对照表

场景输入样例推荐方案禁止
标准 JSON 数组字符串'["a","b"]'json.loads()eval、split 粗暴分割
Python 打印输出对象字符串 [Document(...)]str([Document()])1. 优先使用 model_dump 做标准 JSON 序列化;2. 本地调试可信数据用 eval;3. 外部来源使用正则提取用户输入下使用 eval

关键安全原则

  1. 但凡字符串来自用户、接口请求、外部网络,永远不要调用eval()
  2. 尽量不要依赖str(对象)的输出做解析,打印输出是给人看的,不是用来程序解析;
  3. 对象持久化,优先转为标准 JSON 结构,避免后面各种奇葩解析难题。

拓展思考

1️⃣ 什么时候用ast.literal_eval? 当你拿到的字符串是单引号的 python 字面量,比如"{'name':'test'}",不是标准 JSON,可以使用ast.literal_eval安全解析字典,它可以处理单引号字典,且不会执行代码。

import ast
d = ast.literal_eval("{'page': 2}")

2️⃣ RAG 开发中很多坑都来自序列化:向量库返回 Document、LLM 输出字符串、缓存数据,一定要做好边界处理。

完整可运行代码汇总

# ========== 第一题 标准JSON字符串转list ==========
import json
raw_str ='["qwen-turbo","qwen-plus","deepseek"]'
model_list = json.loads(raw_str)
print("第一题结果:", model_list)

# ========== 第二题 字符串化Document,正则提取page和内容 ==========
import re

doc_str = """[Document(metadata={'pk': 12, 'page': 2}, page_content='2. 费⽤报销: 业务活动中产⽣的费⽤,须凭真 实、合规的发票,并写明事由,经审批后⽅可报销。严禁虚报、\\n谎报费⽤。\\n第五章 奖惩制度  \\n第13条 奖励\\n对于符合下列条件的员⼯,公司将视情况给予通报表扬、奖⾦、晋升等奖励:\\n1. 超额完成销售指标,业绩突出者。\\n2. 提出合理化建议,经采纳后为公司带来显著效益者。\\n3. 在维护公司利益和声誉⽅⾯有重 ⼤贡献者。'), Document(metadata={'pk': 27, 'page': 2}, page_content='2. 费⽤报销: 业务活动中产⽣的费⽤,须凭真实、合规的发票,并写明事由,经审批后⽅可报销。严禁虚报、\\n谎报费⽤。\\n第五章 奖惩制度  \\n第13条 奖励\\n对于符合下列条件的员⼯,公司将视情况给予通报表扬、奖⾦、晋升等奖励:\\n1. 超额完成销售指标,业绩突出者。\\n2. 提出合理化建议,经采纳后为公司带来显著效益者。\\n3. 在维护公司利益和 声誉⽅⾯有重⼤贡献者。'), Document(metadata={'pk': 14, 'page': 3}, page_content='3. 最后警告: 情节严重,留司察看。\\n4. ⽴即解雇: 对于严重违纪者,公司有权单⽅⾯⽆条件解除劳动合同,⽆需⽀付经济补偿⾦。严重违纪包括但\\n不限于:\\n贪污、受贿、泄露核⼼商业秘密。\\n虚报业绩、伪造销售记录或报销凭证。\\n⻓期旷⼯(连续三天或⽉累计五天)。\\n严重失职,给公司造成重⼤经济损失或声誉损害。\\n违反国家法律法规,被依法追究刑事责任。\\n第六章 附则  \\n第15条 守则的修订与解释')]"""

page_pattern = re.compile(r"metadata=\{.*?'page':\s*(\d+)")
content_pattern = re.compile(r"page_content='(.*?)'\)", re.S)

page_nums = [int(x) for x in page_pattern.findall(doc_str)]
content_list = content_pattern.findall(doc_str)
concat_text = "\n".join(content_list)

print("\n第二题提取页码:", page_nums)
print("\n拼接后的文档内容:")
print(concat_text)

# ========== 生产环境正确存储Document示范 ==========
from langchain_core.documents import Document

# 模拟原始document对象
docs = [
    Document(metadata={"page":1}, page_content="文档1"),
    Document(metadata={"page":2}, page_content="文档2"),
]
# 对象转可序列化字典
dump_data = [d.model_dump() for d in docs]
json_text = json.dumps(dump_data, ensure_ascii=False)
print("\n✅生产环境序列化结果:", json_text)
# 恢复
recover_docs = [Document(**item) for item in json.loads(json_text)]
print("✅恢复后的对象:", recover_docs)
这份文件是《无人机专业建设整体解决方案》的摘要,主要介绍了无人机专业建设的背景、核心建设内容以及预期效果与保障措施,旨在为读者提供一个全面而精炼的无人机专业建设概览。以下是摘要内容:无人机专业建设背景与需求:城市与产业发展驱动:随着城市进程的加速和航空航天等产业的蓬勃发展,无人机技术作为新兴领域,其应用范围和市场需求不断扩大。特别是在天津等航空航天产业集中的地区,无人机专业人才的需求日益增长,成为推动产业升级和经济发展的关键力量。人才培养现状与挑战:当前,无人机操控技术专业在人才培养方面面临诸多挑战,如人才培养方案需修订、课程体系需重构、兼职教师队伍需壮大等。同时,随着无人机行业的快速发展和生源质量的变,原有的人才培养模式已难以满足市场需求,亟需进行创新和改革。核心建设内容与实施策略:课程体系与教学资源建设:基于职业资格标准和实际工作任务分析,构建以无人机操控技术为核心的课程体系,包括无人机模拟飞行、航拍航测、组装调试与维护维修等关键课程。同时,开发优质核心课程,建设信息教学资源库,提高教学质量和效果。实训条件与校企合作:加强校内实训基地建设,配备先进的无人机模拟器和实训设备,满足学生实践操作需求。同时,深校企合作,与多家无人机企业建立长期合作关系,共同开展人才培养、技术研发和社会服务等活动。通过校企合作,实现资源共享、优势互补,推动无人机专业建设与发展。教学团队与师资培养:建立“双专业带头人”制度,培养具有行业影响力的专业带头人和骨干教师。通过企业实践、师资培训、国际交流等途径,提高教师的专业能力和教学水平。同时,完善兼职教师聘用制度,形成专兼职教师共同配合的教学团队,推动教学改革与创新。预期效果与保障措施:预期效果:通过实施无人机专业建设整体解决方案,预计能够培养出具备扎实专业知识、熟练技能和良好综合素质的无人机专业人才。这些人才将能够满足军地两用、企业和社会对无人机技术的需求,推动无人机行业的持续发展和创新。保障措施:为确保无人机专业建设顺利实施并取得预期效果,采取了一系列保障措施。包括建立完善的校企合作机制、顶岗实习制度和社会服务体系;引入第三方社会评价,构建教学质量保障与质量监控体系;制定并实施专业建设项目定期检查制度和绩效考评制度等。这些措施将为无人机专业建设提供有力保障和支持。综上所述,无人机专业建设整体解决方案旨在培养适应市场需求的高素质无人机专业人才,推动无人机行业的持续发展和创新。通过实施核心建设内容与策略,加强实训条件与校企合作,完善教学团队与师资培养等措施,预计能够取得显著的预期效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值