如其他答案所述,大括号扩展是glob的预处理步骤:您扩展所有大括号,然后在每个结果上运行glob。(大括号扩展将一个字符串转换为字符串列表。)
Orwellophile推荐使用braceexpand库。对我来说,这似乎太小的问题来证明依赖关系(尽管这是一个常见的问题,理想情况下应该打包在glob模块中的标准库中)。
因此,以下是一种用几行代码完成的方法。
import itertools
import re
def expand_braces(text, seen=None):
if seen is None:
seen = set()
spans = [m.span() for m in re.finditer("\{[^\{\}]*\}", text)][::-1]
alts = [text[start + 1 : stop - 1].split(",") for start, stop in spans]
if len(spans) == 0:
if text not in seen:
yield text
seen.add(text)
else:
for combo in itertools.product(*alts):
replaced = list(text)
for (start, stop), replacement in zip(spans, combo):
replaced[start:stop] = replacement
yield from expand_braces("".join(replaced), seen)
text_to_expand = "{{pine,}apples,oranges} are {tasty,disgusting} to m{}e }{"
for result in expand_braces(text_to_expand):
print(result)
打印
pineapples are tasty to me }{
oranges are tasty to me }{
apples are tasty to me }{
pineapples are disgusting to me }{
oranges are disgusting to me }{
apples are disgusting to me }{
这里发生的事情是:
- 嵌套括号可能会产生非唯一结果,因此我们使用“seen”仅生成尚未被看到的结果。
- “spans”是“text”中所有最内层、平衡括号的起始和停止索引。通过“[::-1]”切片反转顺序,使索引从最高到最低(稍后将相关)。
- “alts”的每个元素都是逗号分隔的备选项对应的列表。
- 如果没有匹配项(“text”不包含平衡括号),则生成“text”本身,确保它通过“seen”是唯一的。
- 否则,使用“itertools.product”来迭代逗号分隔备选项的笛卡尔积。
- 用当前备选项替换花括号中的文本。由于我们在原地替换数据,所以它必须是可变序列(“list”,而不是“str”),并且我们必须先替换最高索引。如果我们先替换最低索引,那么后面的索引就会改变为它们在“spans”中的值。这就是为什么在首次创建“spans”时要反转它的原因。
- “text”可能包含花括号中的花括号。正则表达式只找到不包含其他花括号的平衡花括号,但嵌套的花括号是合法的。因此,我们需要递归直到没有嵌套的花括号(“len(spans) == 0”情况)。使用Python生成器进行递归时,使用“yield from”从递归调用中重新生成每个结果。
在输出中,“{{pine,}apples,oranges}”首先扩展为“{pineapples,oranges}”和“{apples,oranges}”,然后扩展每个结果。如果我们没有使用“seen”请求唯一结果,则“oranges”结果将出现两次。
像“m{}e”中的空括号会扩展为空,所以这只是“me”。
不平衡的括号,如“}{”,保持不变。
如果需要大型数据集的高性能,则不要使用此算法,但它是一个适用于相当大小数据的通用解决方案。
fnmatch
模块(由glob
用于实现文件名匹配)远不如支持{...}
大括号扩展语法那么复杂。 - Martijn Pieters