使用callable_iterator(re.finditer)导致Python冻结求助
问题:正则表达式迭代时程序冻结
现象描述
编写了文本行处理函数,使用re.finditer获取匹配迭代器时,打印迭代器对象正常,但将迭代器转为列表或循环迭代时,程序出现冻结。该问题仅在特定输入下触发:
- 正常运行:
tokenize_line('$color AS $length') - 触发冻结:
tokenize_line('FALSE + $length IS GT 7 + $length IS 4')
核心代码片段
原始函数:
def tokenize_line(line: str, cmd = ''): matches = re.finditer(Patterns.SUPPORTED_TOKENS, line) tokens_found, not_found, start_idx = [], [], 0 print(matches) for match in matches: pass # 剩余代码
触发冻结的操作:
# 转换为列表时冻结 matches = list(re.finditer(Patterns.SUPPORTED_TOKENS, line)) # 循环迭代时冻结 for match in matches: print(match)
使用的正则表达式模式
(°p\d+°|°a\d+°|°m\d+°)|((?<!\S)(?:!\'(?:\\.|[^\'\n\\])*\'|!\"(?:\\.|[^\n\"\\])*\")(?!\S))|((?:\'(?:\\.|[^\'\n\\])*\'|\"(?:\\.|[^\n\"\\])*\"))|((\{(.*)\}))|((?<!\S)([@$][\w]*(?:\.[\w]*)*)(?!\S))|((?<!\d)-?\d*\.?\d+)|(\*\*|[\+\-\*\(\)/%\^]|==|&&|\|\||!=|>=|<=|>|<<|~~|!~~|::|!::)|([\:/])|(\b(?:AS|AND|AT|:|BETWEEN|BY|FROM|IN|INTO|ON|OF|OR|THAN|TO|USING|WITH)\b)|(\b[a-zA-Z_][a-zA-Z0-9_]* *((?:[^;()\'\"\"]*|\"(?:[^\"\\]|\\.)*\"|\'(?:[^\'\\]|\\.)*\'|\([^)]*\))*?;))|((\b(?:EMPTY|STRING|NUMBER|BOOL|ARRAY|MAP|TRUE|FALSE|NULL|UNKNOWN|DOTALL|IGNORECASE|MULTILINE|ARRAY_ARRAY|ARRAY_STRING|ARRAY_MAP|ARRAY_NUMBER|ARRAY_NULL|DOT|SPACE|NEWLINE|SEMICOLON|COLON|HASH|COMMA|TAB)\b)|(\b(?:IS NOT LT|IS NOT GT|IS NOT GEQ|IS NOT LEQ|IS NOT|IS LT|IS GT|IS GEQ|IS LEQ|IS|NOT IN|NOT|IN|HAS NOT|HAS|AND|OR)\b))
模式各部分说明
- 自定义令牌:匹配
°p\d+°、°a\d+°、°m\d+°格式的令牌 - 带感叹号的引号字符串:匹配前后无空白字符的
!'...'或!"..."格式字符串,支持转义 - 普通引号字符串:匹配单/双引号包裹的字符串,支持转义
- 花括号内容:匹配
{...}内的所有内容 - 变量:匹配前后无空白字符的
@/$开头变量,支持嵌套属性(如$obj.attr) - 数字:匹配整数、浮点数及负数
- 运算符:匹配各类数学/逻辑运算符(如
**、&&、>=等) - 标点符号:匹配
:和/ - 保留关键字:匹配AS、AND、BETWEEN等语言关键字
- 函数定义:匹配符合语法的函数定义结构(如
func(...);) - 数据类型与逻辑运算符:匹配EMPTY、TRUE等类型关键字,以及IS NOT LT、HAS等复杂逻辑运算符
问题成因
核心是正则表达式的灾难性回溯,由两部分导致:
- 函数定义分支的贪婪嵌套匹配:函数定义分支中的
[^;()\'\"\"]*与嵌套的\([^)]*\)组合,在遇到多运算符/空格的文本时,会尝试大量无效匹配路径,消耗CPU资源 - 逻辑运算符分支的重叠匹配:最后一个分支中存在大量重叠关键字(如
AND同时出现在保留关键字和逻辑运算符分支),且长关键字(如IS NOT LT)与短关键字(如IS)并存,导致引擎反复尝试不同匹配组合,陷入无限回溯
解决方案
1. 优化正则表达式结构
调整匹配顺序:优先匹配长模式
将长逻辑运算符(如IS NOT LT)放在短关键字(如IS)之前,避免引擎先匹配短模式后回溯:
# 原逻辑运算符分支 (\b(?:IS NOT LT|IS NOT GT|IS NOT GEQ|IS NOT LEQ|IS NOT|IS LT|IS GT|IS GEQ|IS LEQ|IS|NOT IN|NOT|IN|HAS NOT|HAS|AND|OR)\b) # 优化后(按长度从长到短排序) (\b(?:IS NOT LT|IS NOT GT|IS NOT GEQ|IS NOT LEQ|IS NOT|IS LT|IS GT|IS GEQ|IS LEQ|NOT IN|HAS NOT|IS|NOT|IN|HAS|AND|OR)\b)
使用原子组禁止回溯
用(?>...)原子组包裹内部匹配逻辑,减少无效回溯。例如优化引号字符串匹配:
# 原引号字符串 (?:\'(?:\\.|[^\'\n\\])*\'|\"(?:\\.|[^\n\"\\])*\") # 优化为原子组 (?:\'(?>\\.|[^\'\n\\])*\'|\"(?>\\.|[^\n\"\\])*\")
简化函数定义分支
将函数定义分支中的非贪婪匹配改为更严格的原子组匹配:
# 原函数定义分支 (\b[a-zA-Z_][a-zA-Z0-9_]* *((?:[^;()\'\"\"]*|\"(?:[^\"\\]|\\.)*\"|\'(?:[^\'\\]|\\.)*\'|\([^)]*\))*?;)) # 优化后 (\b[a-zA-Z_][a-zA-Z0-9_]* *+(?>[^;()\'\"\"\\]*(?:(?:\"(?>[^\"\\]+|\\.)*\"|\'(?>[^\'\\]+|\\.)*\'|\((?>[^()]+|\((?1)\))*\))[^;()\'\"\"\\]*)*?;)
移除冗余分支
删除重复的关键字(如AND、OR同时出现在两个分支),保留一处即可。
2. 预编译正则表达式
预编译正则表达式可避免重复编译开销,提升匹配效率:
import re # 预编译优化后的正则 SUPPORTED_TOKENS_PATTERN = re.compile(OPTIMIZED_REGEX) def tokenize_line(line: str, cmd = ''): if not line: return [], [] matches = list(SUPPORTED_TOKENS_PATTERN.finditer(line)) # 后续处理逻辑
3. 拆分正则表达式(可选)
若单一大正则仍有性能问题,可拆分为多个小正则按优先级依次匹配:
PATTERNS = [ re.compile(r'°p\d+°|°a\d+°|°m\d+°'), re.compile(r'(?<!\S)(?:!\'(?>\\.|[^\'\n\\])*\'|!\"(?>\\.|[^\n\"\\])*\")(?!\S)'), re.compile(r'\'(?>\\.|[^\'\n\\])*\'|\"(?>\\.|[^\n\"\\])*\"'), # 其他模式按优先级排序 ] def tokenize_line(line: str, cmd = ''): tokens_found = [] pos = 0 while pos < len(line): matched = False for pattern in PATTERNS: match = pattern.match(line, pos) if match: tokens_found.append(match.group()) pos = match.end() matched = True break if not matched: pos += 1 return tokens_found, []
优化后的完整正则示例
(°p\d+°|°a\d+°|°m\d+°)|((?<!\S)(?:!\'(?>\\.|[^\'\n\\])*\'|!\"(?>\\.|[^\n\"\\])*\")(?!\S))|((?:\'(?>\\.|[^\'\n\\])*\'|\"(?>\\.|[^\n\"\\])*\"))|((\{(.*)\}))|((?<!\S)([@$][\w]*(?:\.[\w]*)*)(?!\S))|((?<!\d)-?\d*\.?\d+)|(\*\*|[\+\-\*\(\)/%\^]|==|&&|\|\||!=|>=|<=|>|<<|~~|!~~|::|!::)|([\:/])|(\b(?:AS|AND|AT|:|BETWEEN|BY|FROM|IN|INTO|ON|OF|OR|THAN|TO|USING|WITH)\b)|(\b[a-zA-Z_][a-zA-Z0-9_]* *+(?>[^;()\'\"\"\\]*(?:(?:\"(?>[^\"\\]+|\\.)*\"|\'(?>[^\'\\]+|\\.)*\'|\((?>[^()]+|\((?1)\))*\))[^;()\'\"\"\\]*)*?;)|\b(?:IS NOT LT|IS NOT GT|IS NOT GEQ|IS NOT LEQ|IS NOT|IS LT|IS GT|IS GEQ|IS LEQ|NOT IN|HAS NOT|IS|NOT|IN|HAS)\b
内容的提问来源于stack exchange,提问作者amarodev05
相关产品推荐
相关产品推荐

