Python 字符串转对象的多种解法——从 JSON 到 Document 对象 在 Python 开发中我们经常遇到字符串看起来像某种数据结构list、dict、对象但实际类型却是str的情况。如何安全、高效地将这些字符串还原为真正的 Python 对象本文通过两道实战题目带你掌握 5 种核心解法。题目一JSON 字符串转列表题目描述给定一个 JSON 格式的字符串将其转换为 Python 列表raw_str[qwen-turbo,qwen-plus,deepseek]解法一json.loads()推荐 ⭐⭐⭐⭐⭐importjson raw_str[qwen-turbo,qwen-plus,deepseek]resultjson.loads(raw_str)print(result)# [qwen-turbo, qwen-plus, deepseek]print(type(result))# class listprint(type(result[0]))# class str优点标准库内置无需额外安装安全可靠不会执行恶意代码支持 JSON 所有数据类型str、int、float、bool、list、dict、null适用场景数据来自 API 接口、配置文件、数据库等标准 JSON 格式。解法二ast.literal_eval()推荐 ⭐⭐⭐⭐importast raw_str[qwen-turbo,qwen-plus,deepseek]resultast.literal_eval(raw_str)print(result)# [qwen-turbo, qwen-plus, deepseek]print(type(result))# class list优点支持更广泛的 Python 字面量tuple、set、frozenset、bytes、complex比eval()安全只解析字面量不执行函数调用缺点比json.loads()稍慢不支持 JSON 特有的null、true、falsePython 中对应None、True、False适用场景字符串是 Python 语法格式如单引号、元组、集合等而非严格 JSON。解法三eval()不推荐 ❌raw_str[qwen-turbo,qwen-plus,deepseek]resulteval(raw_str)print(result)# [qwen-turbo, qwen-plus, deepseek]严重安全隐患# 如果字符串来自用户输入可能包含恶意代码malicious_str__import__(os).system(rm -rf /)eval(malicious_str)# 执行系统命令数据全删结论永远不要对不可信输入使用eval()解法四正则表达式提取兜底方案importre raw_str[qwen-turbo,qwen-plus,deepseek]# 提取引号内的内容resultre.findall(r([^]),raw_str)print(result)# [qwen-turbo, qwen-plus, deepseek]适用场景字符串格式不规范不是标准 JSON但需要提取部分内容。缺点无法处理嵌套结构如 list 里包含 dict。解法五字符串替换 split最原始raw_str[qwen-turbo,qwen-plus,deepseek]# 去掉首尾方括号去掉引号按逗号分割resultraw_str.strip([]).replace(,).split(,)print(result)# [qwen-turbo, qwen-plus, deepseek]缺点极易出错无法处理包含逗号或引号的元素。仅适用于最简单、格式完全可控的场景。第一题解法对比总结解法安全性速度适用场景推荐度json.loads()高快标准 JSON⭐⭐⭐⭐⭐ast.literal_eval()高中Python 字面量⭐⭐⭐⭐eval()极低快绝不使用❌正则表达式中中非标准格式⭐⭐字符串操作高快极简场景⭐题目二Document 对象 repr 字符串解析题目描述给定一个字符串其中包含多个Document对象的 repr 表示需要提取所有Document对象获取每个Document的page_content按page排序后拼接raw_documents[Document(metadata{pk: 12, page: 2}, page_content第一章...), Document(metadata{pk: 27, page: 2}, page_content第二章...), Document(metadata{pk: 14, page: 3}, page_content第三章...)]核心难点这不是标准 JSON而是 Python 对象的repr()输出包含单引号字符串类名Document(...)嵌套的metadata字典换行符\n解法一正则表达式提取推荐 ⭐⭐⭐⭐importrefromdataclassesimportdataclassdataclassclassDocument:metadata:dictpage_content:strraw_documents[Document(metadata{pk: 12, page: 2}, page_content第一章内容...), Document(metadata{pk: 27, page: 2}, page_content第二章内容...), Document(metadata{pk: 14, page: 3}, page_content第三章内容...)]defparse_documents_regex(raw_str):使用正则表达式提取 Document 对象documents[]# 匹配 Document(...) 的内容patternrDocument\(metadata\{([^}])\},\s*page_content([^])\)matchesre.findall(pattern,raw_str)formetadata_str,contentinmatches:# 解析 metadata 字典meta{}forpairinmetadata_str.split(,):key,valuepair.strip().split(:)keykey.strip().strip()valueint(value.strip())meta[key]value documents.append(Document(metadatameta,page_contentcontent))returndocuments# 解析docsparse_documents_regex(raw_documents)# 按 page 排序sorted_docssorted(docs,keylambdad:d.metadata[page])# 拼接 page_contentfull_text\n\n.join([doc.page_contentfordocinsorted_docs])print(full_text)优点不依赖外部库安全可控只提取需要的数据缺点正则表达式脆弱格式稍有变化就失效需要手动处理嵌套结构解法二ast.literal_eval() 自定义解析 ⭐⭐⭐⭐⭐importast raw_documents[Document(metadata{pk: 12, page: 2}, page_content第一章内容...), Document(metadata{pk: 27, page: 2}, page_content第二章内容...)]# 步骤 1将 Document(...) 替换为普通字典# 让字符串看起来像 Python 字面量defdocument_to_dict_str(raw_str):将 Document 对象转换为字典格式的字符串importre# 去掉最外层的方括号innerraw_str.strip([])# 分割多个 Documentpartsre.split(r\),\s*Document\(,inner)# 处理首尾parts[0]parts[0].replace(Document(,{)parts[-1]parts[-1].replace(),})foriinrange(1,len(parts)-1):parts[i]{parts[i]# 重新组合为列表dict_str[}, {.join(parts)]returndict_str# 步骤 2使用 ast.literal_eval 安全解析dict_strdocument_to_dict_str(raw_documents)parsedast.literal_eval(dict_str)print(parsed)# [{metadata: {pk: 12, page: 2}, page_content: 第一章内容...}, ...]优点利用ast.literal_eval的安全性比纯正则更健壮缺点需要预处理字符串格式复杂的嵌套结构处理麻烦解法三模拟 Document 类 eval()仅用于学习# 定义 Document 类classDocument:def__init__(self,metadata,page_content):self.metadatametadata self.page_contentpage_contentdef__repr__(self):returnfDocument(metadata{self.metadata}, page_content{self.page_content[:20]}...)raw_documents[Document(metadata{pk: 12, page: 2}, page_content第一章...), ...]# 在受控环境中使用 eval# ⚠️ 仅用于学习生产环境绝对不要对不可信输入使用resulteval(raw_documents,{__builtins__:{}},{Document:Document})print(result[0].page_content)警告此方法需要严格控制执行环境仅适用于完全可信的数据源。解法四如果原始数据源可用——直接从源头解决最佳实践如果这些数据来自某个数据库或 API应该直接从源头获取结构化数据而不是解析 repr 字符串。# 理想情况直接从数据库/ORM 获取fromsqlalchemy.ormimportSessiondefget_documents_from_db(session:Session):从数据库直接查询 Document 对象documentssession.query(Document).order_by(Document.page).all()returndocuments# 拼接text\n\n.join([doc.page_contentfordocindocuments])核心原则repr 字符串是显示给人看的不是给程序解析的。如果可能永远使用原始数据源。第二题完整解决方案importrefromdataclassesimportdataclassfromtypingimportListdataclassclassDocument:metadata:dictpage_content:strdefparse_documents(raw_str:str)-List[Document]: 解析 Document repr 字符串 提取所有 Document 对象获取 page_content 并拼接 documents[]# 匹配 Document(...) 结构patternrDocument\(metadata(\{[^}]\}),\s*page_content((?:[^\\]|\\.)*?)\)matchesre.finditer(pattern,raw_str)formatchinmatches:metadata_strmatch.group(1)page_contentmatch.group(2)# 解析 metadata 字典metadataast.literal_eval(metadata_str)# 处理转义字符page_contentpage_content.replace(\\n,\n)documents.append(Document(metadatametadata,page_contentpage_content))returndocumentsdefmerge_documents(documents:List[Document])-str:按 page 排序并拼接# 按 page 排序sorted_docssorted(documents,keylambdad:d.metadata.get(page,0))# 拼接return\n\n.join([doc.page_contentfordocinsorted_docs])# 完整示例 raw_documents[Document(metadata{pk: 12, page: 2}, page_content2. 费用报销业务活动中产生的费用须凭真实、合规的发票并写明事由经审批后方可报销。严禁虚报、谎报费用。\n第五章 奖惩制度\n第13条 奖励\n对于符合下列条件的员工公司将视情况给予通报表扬、奖金、晋升等奖励\n1. 超额完成销售指标业绩突出者。\n2. 提出合理化建议经采纳后为公司带来显著效益者。\n3. 在维护公司利益和声誉方面有重大贡献者。), Document(metadata{pk: 27, page: 2}, page_content2. 费用报销业务活动中产生的费用须凭真实、合规的发票并写明事由经审批后方可报销。严禁虚报、谎报费用。\n第五章 奖惩制度\n第13条 奖励\n对于符合下列条件的员工公司将视情况给予通报表扬、奖金、晋升等奖励\n1. 超额完成销售指标业绩突出者。\n2. 提出合理化建议经采纳后为公司带来显著效益者。\n3. 在维护公司利益和声誉方面有重大贡献者。), Document(metadata{pk: 14, page: 3}, page_content3. 最后警告情节严重留司察看。\n4. 立即解雇对于严重违纪者公司有权单方面无条件解除劳动合同无需支付经济补偿金。严重违纪包括但不限于\n贪污、受贿、泄露核心商业秘密。\n虚报业绩、伪造销售记录或报销凭证。\n长期旷工连续三天或月累计五天。\n严重失职给公司造成重大经济损失或声誉损害。\n违反国家法律法规被依法追究刑事责任。\n第六章 附则\n第15条 守则的修订与解释)] # 解析docsparse_documents(raw_documents)print(f解析出{len(docs)}个 Document)# 查看fordocindocs:print(f\nPage{doc.metadata[page]}:{doc.page_content[:50]}...)# 拼接full_textmerge_documents(docs)print(f\n\n{*50})print(拼接结果前200字)print(full_text[:200])核心总结安全转换优先级1. json.loads() ← 标准 JSON首选 2. ast.literal_eval() ← Python 字面量安全 3. 数据源直接获取 ← 最理想避免解析字符串 4. 正则表达式 ← 格式不规范时的兜底 5. eval() ← 绝不用于不可信输入关键原则原则说明安全优先永远不要对不可信输入使用eval()标准优先优先使用json.loads()处理标准 JSON源头优先可能的话从原始数据源获取结构化数据验证输出转换后验证类型和内容是否符合预期异常处理用 try-except 包裹解析代码异常处理模板importjsondefsafe_parse_json(raw_str):安全解析 JSON 字符串try:resultjson.loads(raw_str)ifnotisinstance(result,list):raiseValueError(f期望 list得到{type(result)})returnresultexceptjson.JSONDecodeErrorase:print(fJSON 解析失败{e})return[]exceptExceptionase:print(f未知错误{e})return[]# 使用raw_str[qwen-turbo,qwen-plus,deepseek]resultsafe_parse_json(raw_str)实战练习尝试解析以下字符串# 练习 1嵌套 JSONtest1{models: [{name: GPT-4, provider: OpenAI}, {name: Claude, provider: Anthropic}]}# 练习 2混合引号test2[{\id\: 1, \name\: Alice}, {\id\: 2, \name\: Bob}]# 练习 3包含特殊字符test3[Hello\\nWorld, Tab\\tHere, Quote\\\Test\]如果这篇文章对你有帮助欢迎点赞、收藏、关注你的支持是我持续创作的动力。