本文详解如何使用 `re.multiline` 与合理锚点设计,配合非贪婪匹配与负向先行断言,从带 `#` 边界的文本块中稳定提取 start_of_compile 与 end_of_compile 之间的多行主体内容,避开常见 `nonetype` 错误和跨行匹配失效问题。
在处理自定义格式的配置文件或嵌入式脚本(如 .qel 文件)时,常需提取两个固定标记之间的多行内容,同时忽略外围的注释行(如全 # 行)。但直接套用 .* 默认无法跨行匹配,而错误启用 re.MULTILINE(仅影响 ^/$ 行锚定)却忽略 re.DOTALL(即 re.S)语义,是导致 match() 返回 None 的典型原因。
不过,本例中更优解并非盲目启用 re.S——因为原始文本结构高度规整:起始和结束标记均独占一行且由 # 构成,中间内容不包含纯 # 行。因此我们可借助 re.MULTILINE + 行级负向先行断言实现高效、安全的提取,避免 re.S 引入的过度回溯风险。
以下是推荐的正则模式及完整实现:
import re
def extract_compile_block(filepath: str) -> str:
with open(filepath, "r") as f:
content = f.read()
# 关键正则:逐行匹配,确保起始标记后紧跟空行/注释行,再捕获非纯#行内容
pattern = r'^#+\n#+start_of_compile\b.*\n#+\n\s*^(.+(?:\n(?!#+$).*)*)'
match = re.search(pattern, content, re.MULTILINE)
if not match:
raise ValueError("Failed to locate start_of_compile / end_of_compile block")
return match.group(1).strip() # 去除首尾空白(含换行)
# 示例调用
try:
result = extract_compile_block("compile.qel")
print(repr(result)) # 输出: 'parse these lines in between them\n...\n....'
except (FileNotFoundError, ValueError) as e:
print(f"Error: {e}")
✅ 关键设计解析: