【问题标题】:Python regex: splitting on pattern match that is an empty stringPython 正则表达式:拆分为空字符串的模式匹配
【发布时间】:2015-07-11 09:17:22
【问题描述】:

使用 re 模块,我似乎无法拆分空字符串的模式匹配:

>>> re.split(r'(?<!foo)(?=bar)', 'foobarbarbazbar')
['foobarbarbazbar']

也就是说,即使找到了匹配,如果是空字符串,即使re.split也无法拆分字符串。

docs for re.split 似乎支持我的结果。

对于这种特殊情况,很容易找到“解决方法”:

>>> re.sub(r'(?<!foo)(?=bar)', 'qux', 'foobarbarbazbar').split('qux')
['foobar', 'barbaz', 'bar']

但这是一种容易出错的方法,因为这样我就必须提防已经包含要拆分的子字符串的字符串:

>>> re.sub(r'(?<!foo)(?=bar)', 'qux', 'foobarbarquxbar').split('qux')
['foobar', 'bar', '', 'bar']

有没有更好的方法来拆分与re 模块匹配的空模式?此外,为什么re.split 一开始就不允许我这样做?我知道使用正则表达式的其他拆分算法是可能的;例如,我可以使用 JavaScript 的内置 String.prototype.split() 来做到这一点。

【问题讨论】:

    标签: python regex string split


    【解决方案1】:

    不幸的是,split 需要非零宽度匹配,但尚未修复,因为很多不正确的代码取决于当前行为,例如使用 [something]* 作为正则表达式。使用这些模式现在将生成一个FutureWarning,而那些永远可以拆分任何东西的模式,从 Python 3.5 开始抛出一个ValueError

    >>> re.split(r'(?<!foo)(?=bar)', 'foobarbarbazbar')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.6/re.py", line 212, in split
        return _compile(pattern, flags).split(string, maxsplit)
    ValueError: split() requires a non-empty pattern match.
    

    这个想法是,在一段时间的警告之后,可以更改行为,以便您的正则表达式再次起作用。


    如果您不能使用regex 模块,您可以使用re.finditer() 编写自己的拆分函数:

    def megasplit(pattern, string):
        splits = list((m.start(), m.end()) for m in re.finditer(pattern, string))
        starts = [0] + [i[1] for i in splits]
        ends = [i[0] for i in splits] + [len(string)]
        return [string[start:end] for start, end in zip(starts, ends)]
    
    print(megasplit(r'(?<!foo)(?=bar)', 'foobarbarbazbar'))
    print(megasplit(r'o', 'foobarbarbazbar'))
    

    如果您确定匹配仅为零宽度,则可以使用拆分的开头来简化代码:

    import re
    
    def zerowidthsplit(pattern, string):
        splits = list(m.start() for m in re.finditer(pattern, string))
        starts = [0] + splits
        ends = splits + [ len(string) ]
        return [string[start:end] for start, end in zip(starts, ends)]
    
    print(zerowidthsplit(r'(?<!foo)(?=bar)', 'foobarbarbazbar'))
    

    【讨论】:

    • 虽然另一个答案中的findall 方法很聪明,但它需要在同一个正则表达式中重复两次“foo”模式。如果“foo”实际上是更复杂模式的占位符,那将是完全不可取的。对于复杂的正则表达式,这个答案是最具可扩展性和实用性的,它也不需要安装任何额外的模块(这也消除了重构现有代码以使用regex 的必要性),这就是我为什么接受这是最佳答案。
    • @Shashank 添加了一个拆分函数,可以在零宽度和非零宽度匹配中正常工作
    • 不正确的代码怎么会依赖未实现的东西? Python 在客观上很糟糕的领域很少,这是一个很好的例子。
    • @EricDuminil 单个示例使用 [something]* 作为分隔符。无论如何,它是正在修复的。
    【解决方案2】:
    import regex
    x="bazbarbarfoobar"
    print regex.split(r"(?<!baz)(?=bar)",x,flags=regex.VERSION1)
    

    您可以在此处使用regex 模块。

    (.+?(?<!foo))(?=bar|$)|(.+?foo)$
    

    使用re.findall

    See demo

    【讨论】:

    • 你的意思是 PyPI 上的模块应该在未来取代 re
    • 我不得不谷歌它,因为你的答案没有链接。 :p 但这很高兴知道。知道何时安排更换吗?
    • 嗯 foo 需要在捕获组中,所以我将其修复为:re.findall(r'(.+?(?&lt;!foo)|.*?foo(?!bar))(?=bar|$)', 'foo')。我认为可以正常工作。它允许捕获组以 foo 结尾,如果否定前瞻表明它前面没有 bar。
    • @Shashank 似乎工作正常!!! (.+?(?&lt;!foo))(?=bar|$)|(.*?foo)$ 猜测两者的工作方式相同!!!
    • 啊,我明白了,它给出了 [('', 'foo')] 因为当您将多个捕获组与交替运算符绑定在一起时,findall 返回元组。这是不受欢迎的......所以我认为我的方法是最好的,因为它只有一个捕获组。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多