脚本专家 发表于 2026-9-10 13:00:00

Python伪列表字符串解析:json.loads与正则提取

在 Python 开发中,经常遇到看起来像 list、实际类型是 str 的数据。例如接口或日志里出现:


data = '["qwen-turbo","qwen-plus","deepseek"]'
print(type(data))# <class 'str'>


另一种是包含自定义对象的 repr 字符串,例如 LangChain 的 Document:


data = ""


这两类字符串的解析方式不同:标准 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 = ""
pages = re.findall(r"'page':\s*(\d+)", raw_str)
pages =
print(pages)#


正则含义:'page': 匹配字面量;\s* 匹配 0 个或多个空白字符;(\d+) 捕获 1 个或多个数字。

方法 2:提取 page 和 page_content,并按页分组


import re
from collections import defaultdict

raw_str = ""
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.append(content)

for page in sorted(page_contents.keys()):
    combined = "\n".join(page_contents)
    print(f"=== 第 {page} 页 ===")
    print(combined)
    print()


正则 r"Document\(metadata=\{.*?'page':\s*(\d+).*?page_content='(.*?)'\)" 解析:
Document\( 匹配 Document(;
metadata=\{ 匹配 metadata={;
.*? 非贪婪匹配任意字符;
'page':\s*(\d+) 捕获 page 的值;
page_content='(.*?)' 捕获 page_content 的内容;
\) 匹配 )。
re.DOTALL 让 . 可以匹配换行符。

三、完整代码
第一题完整代码:


import json
import ast

def parse_json_list(raw_str: str) -> list:
    """解析 JSON 格式的字符串列表"""
    return json.loads(raw_str)

def parse_python_list(raw_str: str) -> list:
    """解析 Python 字面量格式的字符串列表"""
    return ast.literal_eval(raw_str)

raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
result1 = parse_json_list(raw_str)
result2 = parse_python_list(raw_str)
print(f"json.loads: {result1}")
print(f"ast.literal_eval: {result2}")


第二题完整代码:


import re
from collections import defaultdict

def extract_pages(raw_str: str) -> list:
    """从 Document 字符串中提取所有 page"""
    return

def extract_docs_by_page(raw_str: str) -> dict:
    """从 Document 字符串中提取 page 和 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.append(content)
    return dict(page_contents)

def combine_by_page(raw_str: str) -> str:
    """按页码拼接所有内容"""
    page_contents = extract_docs_by_page(raw_str)
    result = []
    for page in sorted(page_contents.keys()):
      combined = "\n".join(page_contents)
      result.append(f"=== 第 {page} 页 ===\n{combined}")
    return "\n\n".join(result)

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. 最后警告...')]"""
print("提取的页码:", extract_pages(raw_str))
print("\n按页拼接结果:")
print(combine_by_page(raw_str))


四、对比与选型
纯数据、标准 JSON 字符串:json.loads() 能用,ast.literal_eval() 能用,eval() 也能用但不安全,推荐 json.loads()。
对象格式、Python repr 字符串:json.loads() 不能用,ast.literal_eval() 不能用,eval() 能用但需要上下文中的 Document 且存在安全风险,推荐 re.findall()。

简单选型流程:
遇到字符串形式的“列表”时,先判断格式:
是标准 JSON 格式,用 json.loads();
是 Python 基本字面量,用 ast.literal_eval();
包含自定义对象,用正则表达式提取。

五、常见问题
Q1:为什么不推荐 eval()?
eval() 会执行任意 Python 代码。比如恶意输入:


malicious = "__import__('os').system('rm -rf /')"
eval(malicious)


这类代码会带来严重安全风险。

Q2:正则表达式太难了,有更简单的方法吗?
如果字符串格式固定,也可以用字符串分割:


pages = []
for part in raw_str.split("'page': "):
    if part and part.isdigit():
      pages.append(int(part.split(",")))


但正则更通用、更可靠。

Q3:LangChain 的 Document 对象为什么不是标准 JSON?
因为 Document 是 Python 类,它的 repr() 输出是 Python 语法,不是 JSON。这是 Python 对象序列化的常见问题。

总结:标准格式用标准工具,JSON 字符串用 json.loads();复杂格式用正则,包含自定义对象时正则表达式是最佳选择;安全第一,避免使用 eval(),除非能完全信任输入源。掌握这两类解析技巧后,再遇到“看起来像 list 的字符串”就能按格式选择方法。

热心网友1 发表于 2026-9-10 19:00:00

Re: Python伪列表字符串解析:json.loads与正则提取

楼主把两类“伪列表字符串”分开处理,思路很清楚。标准 JSON 数组优先用 json.loads 是合适的,ast.literal_eval 适合 Python 字面量而且更安全,eval 确实应该尽量避开。Document 这种 repr 字符串不是 JSON,也不是纯字面量,json.loads 和 literal_eval 都会失败,用正则提取字段是比较务实的做法。 只取 page 时直接 findall 最省事;要同时取 page 和 page_content,用 DOTALL 加非贪婪匹配在格式稳定时够用,但要注意 page_content 里如果出现单引号、右括号或换行,可能会提前截断。如果担心健壮性,可以先按 Document( 切分,再逐段提取,会更可控。最后用 defaultdict 按页码聚合,再 sorted 输出,顺序和结构都很自然。 另外如果原始字符串里真的带有 &quot; 这类 HTML 实体,记得先 html.unescape 再 json.loads,否则也会解析失败。整体上这个分类和方案对实际开发挺有参考价值。

热心网友4 发表于 2026-9-10 19:10:00

Re: Python伪列表字符串解析:json.loads与正则提取

这篇整理得很实用,json.loads、ast.literal_eval 和 eval 的取舍讲得很清楚,尤其强调 eval 的安全风险这点很有必要。Document 那种 repr 字符串确实不能当 JSON 处理,用正则先抓 page 和 page_content 再按页分组,思路很直接。一个小建议:如果 page_content 里可能包含单引号、换行或类似 page_content='...' 的片段,正则里的 .*? 可能会提前截断,实际用的时候最好先确认日志格式是否稳定,或者考虑把 Document 的 repr 输出改成结构化日志。最后按页拼接那部分好像没贴完,期待补全。

热心网友4 发表于 2026-9-10 19:20:00

Re: Python伪列表字符串解析:json.loads与正则提取

楼主这个整理挺实用的,json.loads、ast.literal_eval 和 eval 的取舍讲得很清楚,尤其是生产环境尽量避开 eval 这点很重要。Document 那种 repr 字符串确实不能走 JSON 和 literal_eval,用正则提取字段是很常见的应急方案,但实际用的时候要留意 page_content 里如果出现单引号、右括号或换行,非贪婪匹配可能会提前截断,最好根据日志格式把边界写得更确定一些,或者先按 Document(...) 切段再分别解析。最后按页分组那段好像没贴完,期待补上完整的 defaultdict 合并和排序输出示例。
页: [1]
查看完整版本: Python伪列表字符串解析:json.loads与正则提取