在 Python 开发中,经常遇到看起来像 list、实际类型是 str 的数据。例如接口或日志里出现:
- data = '["qwen-turbo","qwen-plus","deepseek"]'
- print(type(data)) # <class 'str'>
复制代码
另一种是包含自定义对象的 repr 字符串,例如 LangChain 的 Document:
- data = "[Document(metadata={'pk': 12, 'page': 2}, page_content='...')]"
复制代码
这两类字符串的解析方式不同:标准 JSON 数组可以用 json.loads(),而 Python 对象 repr 字符串需要正则提取字段。下面按两种场景说明。
一、标准 JSON 格式字符串转列表
题目:
- raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
- # 将其转换为 list
复制代码
这是标准 JSON 格式字符串,内容是字符串数组。
方法一:json.loads() 推荐
- import json
- raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
- result = json.loads(raw_str)
- print(result) # ['qwen-turbo', 'qwen-plus', 'deepseek']
- print(type(result)) # <class 'list'>
复制代码
优点:速度快,标准库支持,JSON 格式通用性强。
方法二:ast.literal_eval()
- import ast
- raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
- result = ast.literal_eval(raw_str)
- print(result) # ['qwen-turbo', 'qwen-plus', 'deepseek']
- print(type(result)) # <class 'list'>
复制代码
优点:安全性高,只解析字面量,不会执行恶意代码。
方法三:eval() 不推荐
- raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
- result = eval(raw_str)
- print(result) # ['qwen-turbo', 'qwen-plus', 'deepseek']
复制代码
缺点:会执行任意 Python 代码,有安全风险,生产环境慎用。
三种方法对比:json.loads() 速度最快、安全性较好,适合 JSON 格式字符串;ast.literal_eval() 速度一般、最安全,适合 Python 字面量格式;eval() 速度一般、危险,不推荐使用。
二、对象格式字符串提取字段
题目:
- raw_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. 最后警告:情节严重,留司察看...')]"""
复制代码
问题是:如何获取里面的 page 属性,并按页码组织 page_content?
这个字符串包含 LangChain 的 Document 对象。它不是标准 JSON,而是 Python 的 repr() 输出格式。
为什么 json.loads() 不行?因为字符串中使用单引号,JSON 要求双引号;Document(...) 这种自定义对象也不是 JSON 支持的数据类型。调用 json.loads(raw_str) 会报 json.decoder.JSONDecodeError。
为什么 ast.literal_eval() 也不行?因为它只能解析 Python 字面量,例如字符串、数字、列表、字典等,不能解析函数调用 Document(...)。调用 ast.literal_eval(raw_str) 会报 ValueError: malformed node。
解决方案是使用正则表达式。
方法 1:只提取 page
- import re
- raw_str = "[Document(metadata={'pk': 12, 'page': 2}, page_content='...'), ...]"
- pages = re.findall(r"'page':\s*(\d+)", raw_str)
- pages = [int(p) for p in pages]
- print(pages) # [2, 2, 3]
复制代码
正则含义:'page': 匹配字面量;\s* 匹配 0 个或多个空白字符;(\d+) 捕获 1 个或多个数字。
方法 2:提取 page 和 page_content,并按页分组
[code]
import re
from collections import defaultdict
raw_str = "[Document(metadata={'pk': 12, 'page': 2}, page_content='...'), ...]"
docs = re.findall(
r"Document\(metadata=\{.*?'page':\s*(\d+).*?page_content='(.*?)'\)",
raw_str,
re.DOTALL
)
page_contents = defaultdict(list)
for page, content in docs:
page_contents[int(page)].append(content)
for page in sorted(page_contents.keys()):
combined = "\n".join(page_contents |