在Python

时间:2017-03-27 03:22:59

标签: python regex stream

我有数据流连续进入多个TCP套接字。对于每一个,我有一个不同的正则表达式,我需要拉出匹配。例如,可以匹配格式##。#的数字,后跟字母f:

r = re.compile(rb'([0-9][0-9]\.[0-9])f')

另一个可能匹配格式为###的数字,前面有字母Q:

r = re.compile(rb'Q([0-9][0-9][0-9])')

实际上,表达式可能具有任意长度和复杂性,并且从配置文件中提取并且事先不知道。它们不是硬编码的。

当新数据进入时,我将其附加到bytearray()类型的缓冲区(此处称为self.buffer)。然后我调用这样的函数(self.r是编译的正则表达式):

def advance(self):
    m = self.r.search(self.buffer)

    # No match. Return.
    if m is None:
        return None

    # Match. Advance the buffer and return the matched groups.
    self.buffer = self.buffer[m.end():]
    return m.groups()

如果还没有匹配,则返回None。如果匹配,则返回匹配并丢弃缓冲区直到匹配结束,准备好再次调用。

然而,这种方法并不是特别有效。问题是必须一次又一次地扫描缓冲区 - 每当有新数据进入时 - 直到找到匹配为止。在找到匹配项并且可以提前启动缓冲区之前,这可能会重复扫描数千次,并且会重复扫描数百万个字符。

我不能在找到匹配项之前简单地丢弃缓冲区的内容,因为匹配可能需要缓冲区的最后几个字节(甚至整个缓冲区)。一旦有更多数据进入,缓冲区的结尾可能是匹配的开始。

如何重写我的" advance"函数安全地丢弃缓冲区中永远不会包含正则表达式开头的部分,这样就不需要重复扫描整个缓冲区了吗?

一种可能性:是否有替代"搜索"返回"无"以外的其他内容如果没有找到匹配的原因是因为字符串的结尾已到达?如果是这样,我可以获得潜在匹配的起始位置吗?这样我就可以放弃缓冲区了。

另一种可能性:某种类型的库足够聪明,可以重写任意正则表达式,因此它们可以在截断的字符串上以不同的 - 可检测的 - 匹配方式。

我也会接受其他可能性,但他们确实需要使用任意正则表达式而不仅仅是上面的简单表达式。理想情况下,他们也不会涉及扫描缓冲区两次(一次找到实际的潜在匹配,一次丢弃东西)。

1 个答案:

答案 0 :(得分:4)

第三方regex模块(不是re)提供部分匹配支持,这是部分解决方案。 (Lookbehinds,^锚点,零宽度匹配以及\b / \B锚点都会在您尝试丢弃开头时以微妙或不那么微妙的方式突破窗口并继续搜索。到目前为止,我已经想到了多少边缘情况,如果还有更多,我不会感到惊讶。)

如果您将partial=True传递给regex.matchregex.searchregex.fullmatchregex.finditer,那么除了报告普通的完整匹配之外,它还会报告不匹配的内容,但如果字符串被扩展则可以:

In [8]: import regex

In [9]: regex.search(r'1234', '', partial=True)
Out[9]: <regex.Match object; span=(0, 0), match='', partial=True>

In [10]: regex.search(r'1234', '12', partial=True)
Out[10]: <regex.Match object; span=(0, 2), match='12', partial=True>

In [11]: regex.search(r'1234', '12 123', partial=True)
Out[11]: <regex.Match object; span=(3, 6), match='123', partial=True>

In [12]: regex.search(r'1234', '1234 123', partial=True)
Out[12]: <regex.Match object; span=(0, 4), match='1234'>

您可以使用匹配对象的partial属性确定匹配是部分还是完整:

In [13]: regex.search(r'1234', '12 123', partial=True).partial
Out[13]: True

In [14]: regex.search(r'1234', '1234 123', partial=True).partial
Out[14]: False

如果更多数据可能会更改匹配结果,则会将匹配报告为部分匹配:

In [21]: regex.search(r'.*', 'asdf', partial=True)
Out[21]: <regex.Match object; span=(0, 4), match='asdf', partial=True>

In [22]: regex.search(r'ham(?: and eggs)?', 'ham', partial=True)
Out[22]: <regex.Match object; span=(0, 3), match='ham', partial=True>

或者如果更多数据可能导致匹配不匹配:

In [23]: regex.search(r'1(?!234)', '1', partial=True)
Out[23]: <regex.Match object; span=(0, 1), match='1', partial=True>

In [24]: regex.search(r'1(?!234)', '13', partial=True)
Out[24]: <regex.Match object; span=(0, 1), match='1'>

当您到达数据流的末尾时,您应该关闭partial以让regex知道这是结束,因此部分匹配不会隐藏完整的匹配。

使用部分匹配信息,您可以放弃部分匹配开始之前的所有内容,并且知道所有丢弃的数据都不会匹配...但是后台可能需要这些数据,所以它需要凌乱如果你这样做,需要额外的工作来支持lookbehinds。 ^也会因字符串更改的开始而感到困惑,\b / \B将无法知道丢弃数据末尾是否有单词字符,并且它无论你选择什么样的“正确”定义,都要使零宽度匹配行为正确。我怀疑如果你以这种方式丢弃数据,其他一些高级功能regex可能也会奇怪地互动; regex有很多功能。

相关问题