Python 2.7.3与3.6.3正则表达式差异:同脚本结果不同
看起来你碰到了Python版本间正则引擎行为差异的坑——同一个脚本在2.7.3里能正确匹配到188组结果,但3.6.3里却少了一大半,而且最小复现案例还没法重现,这确实头疼。结合你的场景和grep的验证结果,我来分析下最可能的原因和解决办法:
核心疑点:编码解析差异
Python 2和3对文件读取的默认处理有个关键区别:
- Python 2的
open()默认返回字节串(str类型),用系统默认编码解析文件; - Python 3的
open()默认返回Unicode字符串(str类型),用UTF-8编码解析。
如果你的目标文件里存在非ASCII字符(比如某些特殊符号、控制字符),两个版本解析后得到的字符串会不一样,直接干扰正则匹配的结果。
验证方法
在Python 3里强制用二进制模式读取文件(和Python 2的默认行为对齐),看看匹配结果是否变回188:
import re inputfile = "opt-guess-firsttetint-r-h2o.out" # Python 3用二进制模式读取,得到字节串 with open(inputfile,"rb") as input_file: input_string = input_file.read() match_geometry = list(re.findall(b'CARTESIAN COORDINATES \(ANGSTROEM\)(.*?)CARTESIAN COORDINATES \(A\.U\.\)', input_string, re.DOTALL)) match_energy = list(re.findall(b'FINAL SINGLE POINT ENERGY(.*?)-------------------------', input_string, re.DOTALL)) print(len(match_geometry)) print(len(match_energy))
如果结果变成188/188,那就是编码解析的锅。此时只要统一两个版本的编码处理即可:比如在Python 2里显式用UTF-8解码,或者在Python 3里用二进制模式处理。
更可靠的正则写法:替换非贪婪匹配为负向预查
非贪婪匹配.*?虽然好用,但在不同正则引擎(或版本)里的边界处理可能有细微差异。换成负向预查的写法,能确保匹配逻辑更健壮,不受版本影响:
import re inputfile = "opt-guess-firsttetint-r-h2o.out" with open(inputfile,"r") as input_file: input_string = input_file.read() # 用负向预查确保匹配内容中不包含终止字符串 match_geometry = re.findall( r'CARTESIAN COORDINATES \(ANGSTROEM\)((?:(?!CARTESIAN COORDINATES \(A\.U\.\)).)*)', input_string, re.DOTALL ) match_energy = re.findall( r'FINAL SINGLE POINT ENERGY((?:(?!-------------------------).)*)', input_string, re.DOTALL ) print(len(match_geometry)) print(len(match_energy))
这种写法的逻辑是:匹配CARTESIAN COORDINATES (ANGSTROEM)之后的所有内容,直到遇到CARTESIAN COORDINATES (A.U.)为止,而且不会因为中间的特殊字符导致提前终止,在Python 2和3中的行为完全一致。
额外排查:文件中的特殊控制字符
如果上面的方法都没用,可以检查文件里是否存在不可见的控制字符(比如\x00、\x1a这类),它们可能在两个版本中被解析成不同的内容,干扰匹配。用下面的代码可以找出这些字符:
with open(inputfile, 'rb') as f: content = f.read() for idx, byte in enumerate(content): # 只打印非打印字符(排除空格、换行、回车和可打印ASCII) if not (32 <= byte <= 126 or byte in (10, 13)): print(f"位置 {idx}: 字节 {hex(byte)}")
如果发现这类字符,可以在读取文件时过滤掉,或者调整正则表达式忽略它们。
内容的提问来源于stack exchange,提问作者Yoda

