在Python中匹配2个正则表达式

时间:2011-09-18 17:48:03

标签: python regex

是否有可能在Python中匹配2个正则表达式?

例如,我有一个用例,我需要比较两个这样的表达式:

re.match('google\.com\/maps', 'google\.com\/maps2', re.IGNORECASE)

我希望能够返回一个RE对象。

但显然,Python期望一个字符串作为第二个参数。 有没有办法实现这一点,或者它是正则表达式匹配方式的限制吗?


背景:我有一个与字符串匹配的正则表达式[r1,r2,r3,...]列表,我需要找出哪个表达式是给定字符串中最具体的匹配。我认为我可以让它发挥作用的方式是:
(1)将r1与r2匹配 (2)然后将r2与r1匹配 如果两者都匹配,我们就会有“平局”。如果只有(1)工作,则r1是比r2“更好”的匹配,反之亦然 我在整个列表中循环(1)和(2)。

我承认这有点让人头晕目眩(主要是因为我的描述可能不连贯),但如果有人能给我一些有关如何实现这一点的见解,我真的很感激。谢谢!

8 个答案:

答案 0 :(得分:15)

re.match的语法澄清之外,我认为我理解你正在努力获取两个或更多未知(用户输入)正则表达式并分类哪个是对字符串更“特定”的匹配。

回想一下,Python正则表达式确实是一种计算机程序。大多数现代形式,包括Python的正则表达式,都基于Perl。 Perl的正则表达式有递归,回溯和其他形式,无视琐碎的检查。实际上,流氓正则表达式可以用作denial of service attack的一种形式。

要在您自己的计算机上查看此内容,请尝试:

>>> re.match(r'^(a+)+$','a'*24+'!')

在我的电脑上大约需要1秒钟。现在将24中的'a'*24增加到更大的数字,比如28。这需要更长的时间。试试48 ...你现在可能需要 CTRL + C 。实际上,随着增加数量的增加,时间增加是指数级的。

您可以在Russ Cox关于'Regular Expression Matching Can Be Simple And Fast'的精彩论文中详细了解此问题。 Russ Cox是2006年构建Google Code Search的Goggle工程师。正如Cox所观察到的,考虑将正则表达式'a?'*33 + 'a'*33'a'*99字符串与awk和Perl(或Python或PCRE或Java或PHP或...)Awk在200微秒内匹配,但由于指数反向跟踪,Perl需要10 15

所以结论是:它取决于! 更具体的匹配是什么意思?在RE2中查看一些Cox的正则表达式简化技术。如果您的项目足够大以编写自己的库(或使用RE2)并且您愿意限制使用的正则表达式语法(即,没有回溯或递归形式),我认为答案是您将“更好的匹配”分类以各种方式。

如果您正在寻找一种简单的方法来说明(regex_3< regex_1< regex_2),当使用Python或Perl的正则表达式语言与某些字符串匹配时,我认为答案是非常非常难(即{ {3}}是this problem

修改

我上面说的一切都是真的!但是,这里有一个根据“特定”形式排序匹配正则表达式的方法:从正则表达式到字符串的编辑数量。编辑的次数越多(或Levenshtein距离越高),正则表达式的“特定”越少。

如果这样做有效(我不知道'具体'对您的申请意味着什么),您将成为法官:

import re

def ld(a,b):
    "Calculates the Levenshtein distance between a and b."
    n, m = len(a), len(b)
    if n > m:
        # Make sure n <= m, to use O(min(n,m)) space
        a,b = b,a
        n,m = m,n

    current = range(n+1)
    for i in range(1,m+1):
        previous, current = current, [i]+[0]*n
        for j in range(1,n+1):
            add, delete = previous[j]+1, current[j-1]+1
            change = previous[j-1]
            if a[j-1] != b[i-1]:
                change = change + 1
            current[j] = min(add, delete, change)      
    return current[n]

s='Mary had a little lamb'    
d={}
regs=[r'.*', r'Mary', r'lamb', r'little lamb', r'.*little lamb',r'\b\w+mb',
        r'Mary.*little lamb',r'.*[lL]ittle [Ll]amb',r'\blittle\b',s,r'little']

for reg in regs:
    m=re.search(reg,s)
    if m:
        print "'%s' matches '%s' with sub group '%s'" % (reg, s, m.group(0))
        ld1=ld(reg,m.group(0))
        ld2=ld(m.group(0),s)
        score=max(ld1,ld2)
        print "  %i edits regex->match(0), %i edits match(0)->s" % (ld1,ld2)
        print "  score: ", score
        d[reg]=score
        print
    else:
        print "'%s' does not match '%s'" % (reg, s)   

print "   ===== %s =====    === %s ===" % ('RegEx'.center(10),'Score'.center(10))

for key, value in sorted(d.iteritems(), key=lambda (k,v): (v,k)):
    print "   %22s        %5s" % (key, value) 

程序正在列出正则表达式并匹配字符串Mary had a little lamb

以下是从“最具体”到“最不具体”的排序排名:

   =====   RegEx    =====    ===   Score    ===
   Mary had a little lamb            0
        Mary.*little lamb            7
            .*little lamb           11
              little lamb           11
      .*[lL]ittle [Ll]amb           15
               \blittle\b           16
                   little           16
                     Mary           18
                  \b\w+mb           18
                     lamb           18
                       .*           22

这基于(可能是简单的)假设:a)从正则表达式本身到匹配子字符串的编辑数(Levenshtein距离)是通配符扩展或替换的结果; b)从匹配的子字符串到初始字符串的编辑。 (只需一个)

作为两个简单的例子:

  1. .*(或.*.*.*?.*等)针对任何刺痛是要获取字符串的大量编辑,实际上等于字符串长度。这是最大可能的编辑,最高分和最少“特定”正则表达式。
  2. 字符串本身对字符串的正则表达式尽可能具体。没有编辑可以将一个更改为另一个,从而导致得分为0或最低。
  3. 如上所述,这很简单。锚点应该增加特异性,但在这种情况下它们不会。非常短的叮咬不起作用,因为外卡可能比字符串长。

    修改2

    我使用Python中未记录的sre_parse模块进行锚解析工作非常好。如果您想要阅读更多内容,请输入>>> help(sre_parse)

    这是re模块底层的goto工人模块。它自2001年以来一直存在于每个Python发行版中,包括所有P3k版本。 可能消失,但我认为不太可能......

    以下是修订后的清单:

    import re
    import sre_parse
    
    def ld(a,b):
        "Calculates the Levenshtein distance between a and b."
        n, m = len(a), len(b)
        if n > m:
            # Make sure n <= m, to use O(min(n,m)) space
            a,b = b,a
            n,m = m,n
    
        current = range(n+1)
        for i in range(1,m+1):
            previous, current = current, [i]+[0]*n
            for j in range(1,n+1):
                add, delete = previous[j]+1, current[j-1]+1
                change = previous[j-1]
                if a[j-1] != b[i-1]:
                    change = change + 1
                current[j] = min(add, delete, change)      
        return current[n]
    
    s='Mary had a little lamb'    
    d={}
    regs=[r'.*', r'Mary', r'lamb', r'little lamb', r'.*little lamb',r'\b\w+mb',
            r'Mary.*little lamb',r'.*[lL]ittle [Ll]amb',r'\blittle\b',s,r'little',
            r'^.*lamb',r'.*.*.*b',r'.*?.*',r'.*\b[lL]ittle\b \b[Ll]amb',
            r'.*\blittle\b \blamb$','^'+s+'$']
    
    for reg in regs:
        m=re.search(reg,s)
        if m:
            ld1=ld(reg,m.group(0))
            ld2=ld(m.group(0),s)
            score=max(ld1,ld2)
            for t, v in sre_parse.parse(reg):
                if t=='at':      # anchor...
                    if v=='at_beginning' or 'at_end':
                        score-=1   # ^ or $, adj 1 edit
    
                    if v=='at_boundary': # all other anchors are 2 char
                        score-=2
    
            d[reg]=score
        else:
            print "'%s' does not match '%s'" % (reg, s)   
    
    print
    print "   ===== %s =====    === %s ===" % ('RegEx'.center(15),'Score'.center(10))
    
    for key, value in sorted(d.iteritems(), key=lambda (k,v): (v,k)):
        print "   %27s        %5s" % (key, value) 
    

    并投了RegEx's:

       =====      RegEx      =====    ===   Score    ===
            Mary had a little lamb            0
          ^Mary had a little lamb$            0
              .*\blittle\b \blamb$            6
                 Mary.*little lamb            7
         .*\b[lL]ittle\b \b[Ll]amb           10
                        \blittle\b           10
                     .*little lamb           11
                       little lamb           11
               .*[lL]ittle [Ll]amb           15
                           \b\w+mb           15
                            little           16
                           ^.*lamb           17
                              Mary           18
                              lamb           18
                           .*.*.*b           21
                                .*           22
                             .*?.*           22
    

答案 1 :(得分:2)

这取决于你有什么样的正则表达式;正如@ carrot-top建议的那样,如果你真的没有处理CS意义上的“正则表达式”,而是有疯狂的扩展,那么你肯定是运气不好。

但是,如果您确实有传统的正则表达式,那么您可能会取得更多进展。首先,我们可以定义“更具体”的含义。假设R是正则表达式,L(R)是由R生成的语言。那么我们可以说如果L(R1)是L(R2)(L(R1)的(严格)子集,则R1比R2更具体。 &lt; L(R2))。这只能让我们到目前为止:在很多情况下,L(R1)既不是L(R2)的子集也不是L(R2)的超集,因此我们可以想象这两者在某种程度上是无法比拟的。举个例子,我试图找到两个匹配的表达式:.*marylamb.*

一个非模棱两可的解决方案是通过实现来定义特异性。例如,将正则表达式以确定性(实现定义)方式转换为DFA并简单地计算状态。不幸的是,这可能对用户来说相对不透明。

实际上,您似乎有一个直观的概念,即您希望如何比较两个正则表达式,特异性。为什么不简单地写下基于正则表达式语法的特异性定义,这种定义与您的直觉相匹配?

完全随意的规则如下:

  1. 字符= 1
  2. n个字符的字符范围= n(让我们说\b = 5,因为我不确定您如何选择将其写出来...手)。
  3. 每个锚点5
  4. *将其参数除以2
  5. +将其参数除以2,然后添加1
  6. . = -10
  7. 无论如何,只需要深思熟虑,因为其他答案很好地概述了你所面临的一些问题;希望它有所帮助。

答案 2 :(得分:1)

我认为这不可能。

另一种方法是尝试计算正则表达式也匹配的长度为n的字符串数。匹配1,000,000,000个长度为15个字符的字符串的正则表达式与仅匹配10个长度为15个字符的字符串的正则表达式不太具体。

当然,除非正则表达式很简单,否则计算可能匹配的数量并非微不足道。

答案 3 :(得分:1)

选项1:

由于用户正在提供正则表达式,或许还要求他们提交一些他们认为可以说明其正则表达式特异性的测试字符串。 (即显示他们的正则表达式比竞争对手的正则表达式更具体。)收集所有用户提交的测试字符串,然后针对整套测试字符串测试所有正则表达式。

为了设计一个好的正则表达式,作者必须考虑到哪些字符串匹配并且与正则表达式不匹配,因此它们应该很容易提供好的测试字符串。


选项2:

您可以尝试蒙特卡罗方法:从两个正则表达式匹配的字符串开始,编写一个生成该字符串突变的生成器(置换字符,添加/删除字符等)如果两个正则表达式匹配或不匹配对于每个突变,同样的方法,然后正则表达“可能绑定”。如果一个匹配另一个没有的突变,反之亦然,那么他们“绝对绑定”。

但是,如果一个人与突变的严格超集相匹配,那么“可能不太具体”比另一个更强。

大量突变后的判决可能并不总是正确的,但可能是合理的。


选项3:

使用ipermute或pyParsing的invert生成与每个正则表达式匹配的字符串。这只适用于使用有限的正则表达式语法子集的正则表达式。

答案 4 :(得分:1)

我认为你可以通过查看与最长结果匹配的结果来做到这一点

>>> m = re.match(r'google\.com\/maps','google.com/maps/hello')
>>> len(m.group(0))
15

>>> m = re.match(r'google\.com\/maps2','google.com/maps/hello')
>>> print (m)
None

>>> m = re.match(r'google\.com\/maps','google.com/maps2/hello')
>>> len(m.group(0))
15

>>> m = re.match(r'google\.com\/maps2','google.com/maps2/hello')
>>> len(m.group(0))
16

答案 5 :(得分:1)

re.match('google\.com\/maps', 'google\.com\/maps2', re.IGNORECASE)

上面的re.match()的第二项是一个字符串 - 这就是为什么它不起作用:正则表达式说匹配谷歌之后的一段时间,但它找到一个反斜杠。你需要做的是加倍正则表达式中用作正则表达式的反斜杠:

def compare_regexes(regex1, regex2):
    """returns regex2 if regex1 is 'smaller' than regex2
    returns regex1 if they are the same
    returns regex1 if regex1 is 'bigger' than regex2
    otherwise returns None"""
    regex1_mod = regex1.replace('\\', '\\\\')
    regex2_mod = regex2.replace('\\', '\\\\')
    if regex1 == regex2:
        return regex1
    if re.match(regex1_mod, regex2):
        return regex2
    if re.match(regex2_mod, regex1):
        return regex1

您可以将退货更改为最适合您需求的退货。哦,并确保你使用re的原始字符串。 r'like this, for example'

答案 6 :(得分:1)

  

是否有可能在Python中匹配2个正则表达式?

这当然是可能的。使用由|加入的括号匹配组进行更改。如果您将最具体的正则表达式的括号匹配组排列为最不具体,则m.groups()返回的元组中的排名将显示您的匹配的具体程度。您还可以使用命名组来命名匹配的具体方式,例如s10表示非常具体,s0表示不太具体的匹配。

>>> s1='google.com/maps2text'
>>> s2='I forgot my goggles at the house'
>>> s3='blah blah blah'
>>> m1=re.match(r'(^google\.com\/maps\dtext$)|(.*go[a-z]+)',s1)
>>> m2=re.match(r'(^google\.com\/maps\dtext$)|(.*go[a-z]+)',s2)
>>> m1.groups()
('google.com/maps2text', None)
>>> m2.groups()
(None, 'I forgot my goggles')
>>> patt=re.compile(r'(?P<s10>^google\.com\/maps\dtext$)|
... (?P<s5>.*go[a-z]+)|(?P<s0>[a-z]+)')
>>> m3=patt.match(s3)
>>> m3.groups()
(None, None, 'blah')
>>> m3.groupdict()
{'s10': None, 's0': 'blah', 's5': None}

如果你没有提前知道哪个正则表达式更具体,那么这是一个难以解决的问题。您想查看this paper,了解正则表达式与文件系统名称匹配的安全性。

答案 7 :(得分:1)

我意识到这是一个非解决方案,但由于没有明确的方法来判断哪个是“最具体的匹配”,当然这取决于用户“意味着什么”,最简单的方法就是要求他们提供自己的优先权。例如,只需将正则表达式按正确的顺序排列即可。然后你可以简单地选择匹配的第一个。如果你希望用户对正则表达式感到满意,那么这可能不是太多问题吗?