是否可以简化这些Python正则表达式?

2 投票
4 回答
1192 浏览
提问于 2025-04-16 23:13
    patterns = {}
    patterns[1] = re.compile("[A-Z]\d-[A-Z]\d")
    patterns[2] = re.compile("[A-Z]\d-[A-Z]\d\d")
    patterns[3] = re.compile("[A-Z]\d\d-[A-Z]\d\d")
    patterns[4] = re.compile("[A-Z]\d\d-[A-Z]\d\d\d")
    patterns[5] = re.compile("[A-Z]\d\d\d-[A-Z]\d\d\d")
    patterns[6] = re.compile("[A-Z][A-Z]\d-[A-Z][A-Z]\d")
    patterns[7] = re.compile("[A-Z][A-Z]\d-[A-Z][A-Z]\d\d")
    patterns[8] = re.compile("[A-Z][A-Z]\d\d-[A-Z][A-Z]\d\d")
    patterns[9] = re.compile("[A-Z][A-Z]\d\d-[A-Z][A-Z]\d\d\d")
    patterns[10] = re.compile("[A-Z][A-Z]\d\d\d-[A-Z][A-Z]\d\d\d")

    def matchFound(toSearch):
        for items in sorted(patterns.keys(), reverse=True):
            matchObject = patterns[items].search(toSearch)
            if matchObject:
                return items
        return 0

然后我用以下代码来查找匹配项:

        while matchFound(toSearch) > 0:

我有10个不同的正则表达式,但我觉得它们可以用一个写得更好、更优雅的正则表达式来替代。你们觉得有可能吗?

编辑:我忘了两个表达式:

  patterns[11] = re.compile("[A-Z]\d-[A-Z]\d\d\d")
  patterns[12] = re.compile("[A-Z][A-Z]\d-[A-Z][A-Z]\d\d\d")

编辑2:我最后得到了以下结果。我意识到我可能会得到额外的结果,但我认为在我解析的数据中不太可能出现。

    patterns = {}
    patterns[1] = re.compile("[A-Z]{1,2}\d-[A-Z]{1,2}\d{1,3}")
    patterns[2] = re.compile("[A-Z]{1,2}\d\d-[A-Z]{1,2}\d{2,3}")
    patterns[3] = re.compile("[A-Z]{1,2}\d\d\d-[A-Z]{1,2}\d\d\d")

4 个回答

3

Josh说得对,至少应该减少正则表达式的数量。

不过,你也可以使用一个范围更大的正则表达式,然后再额外检查所有条件是否都满足。比如说:

pattern = re.compile("([A-Z]{1,2})(\d{1,3})-([A-Z]{1,2})(\d{1,3})")

然后再进行:

matchObject = pattern.search(toSearch)
if matchObject and <do something with the length of the groups, comparing them)>:
    return <stuff>

但即使这样做因为某些原因不奏效,还有其他方法可以改进:

patterns = tuple(re.compile(r) for r in (
    "[A-Z]\d-[A-Z]\d{1,2}",
    "[A-Z]\d\d-[A-Z]\d{2,3}",
    "[A-Z]\d\d\d-[A-Z]\d\d\d",
    "[A-Z][A-Z]\d-[A-Z][A-Z]\d{1,2}",
    "[A-Z][A-Z]\d\d-[A-Z][A-Z]\d{2,3}",
    "[A-Z][A-Z]\d\d\d-[A-Z][A-Z]\d\d\d",
)

def matchFound(toSearch):
    for pat in reversed(patterns):
        matchObject = pat.search(toSearch)
        if matchObject:
            return items # maybe more useful?
    return None
4

Sean Bright给了你需要的答案。这里有个一般性的建议:

Python有很棒的文档。在这种情况下,你可以使用“help”命令来查看:

import re
help(re)

如果你仔细阅读帮助内容,你会看到:

{m,n}    Matches from m to n repetitions of the preceding RE.

使用谷歌也很有帮助。搜索“Python 正则表达式”能找到这些链接:

http://docs.python.org/library/re.html

http://docs.python.org/howto/regex.html

这两个链接都值得一读。

4

Josh Caswell提到,Sean Bright的回答能匹配更多的输入,而不是你最初的那种。抱歉我没有早点发现这个问题。(将来可能需要更详细地描述一下你的问题。)

所以你面临的基本问题是,正则表达式不能进行计数。不过,我们可以在Python中用一种很简洁的方式来解决这个问题。首先,我们要创建一个模式,这个模式可以匹配你所有合法的输入,但也会匹配一些你想要拒绝的输入。

接下来,我们定义一个函数,利用这个模式,然后检查匹配的结果,并进行计数,以确保匹配的字符串符合长度要求。

import re
_s_pat = r'([A-Z]{1,2})(\d{1,3})-([A-Z]{1,2})(\d{1,3})'
_pat = re.compile(_s_pat)

_valid_n_len = set([(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)])
def check_match(s):
    m = _pat.search(s)
    try:
        a0, n0, a1, n1 = m.groups()
        if len(a0) != len(a1):
            return False
        if not (len(n0), len(n1)) in _valid_n_len:
            return False
        return True
    except (AttributeError, TypeError, ValueError):
        return False

下面是对上述代码的一些解释。

首先,我们使用原始字符串来定义模式,然后预编译这个模式。我们可以直接把字符串放到re.compile()的调用中,但我喜欢把它单独放在一个变量里。我们的模式有四个不同的部分,用括号括起来;这些部分会变成“匹配组”。有两个匹配组用来匹配字母字符,还有两个匹配组用来匹配数字。这个模式可以匹配你想要的所有内容,但不会排除一些你不想要的内容。

接下来,我们声明一个set,里面包含所有有效的数字长度。例如,第一组数字可以是1位,第二组可以是2位;这就是(1,2)(一个tuple值)。使用集合是一种很好的方式来指定我们想要的所有合法组合,同时还能快速检查给定的长度对是否合法。

函数check_match()首先使用模式去匹配字符串,返回一个“匹配对象”,并将其绑定到名称m上。如果匹配失败,m可能会被设置为None。我没有直接测试None,而是用了一个try/except块;回想起来,直接测试None可能更好。抱歉,我不是故意让人困惑的。不过,try/except块是一种很简单的方式来处理事情,让它变得很可靠,所以我经常用它来处理类似的情况。

最后,check_match()将匹配组解包成四个变量。两个字母组是a0和a1,两个数字组是n0和n1。然后它检查这些长度是否合法。根据我的理解,规则是字母组需要长度相同;接着我们构建一个数字组长度的tuple,并检查这个tuple是否在我们有效的set中。

这里有一个稍微不同的版本,也许你会更喜欢这个。

import re
# match alpha: 1 or 2 capital letters
_s_pat_a = r'[A-Z]{1,2}'
# match number: 1-3 digits
_s_pat_n = r'\d{1,3}'

# pattern: four match groups: alpha, number, alpha, number
_s_pat = '(%s)(%s)-(%s)(%s)' % (_s_pat_a, _s_pat_n, _s_pat_a, _s_pat_n)
_pat = re.compile(_s_pat)

# set of valid lengths of number groups
_valid_n_len = set([(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)])

def check_match(s):
    m = _pat.search(s)
    if not m:
        return False
    a0, n0, a1, n1 = m.groups()
    if len(a0) != len(a1):
        return False
    tup = (len(n0), len(n1)) # make tuple of actual lengths
    if not tup in _valid_n_len:
        return False
    return True

注意:看起来有效长度的规则其实很简单:

    if len(n0) > len(n1):
        return False

如果这个规则对你有效,你可以去掉集合和元组的部分。嗯,我还会把变量名缩短一点。

import re
# match alpha: 1 or 2 capital letters
pa = r'[A-Z]{1,2}'
# match number: 1-3 digits
pn = r'\d{1,3}'

# pattern: four match groups: alpha, number, alpha, number
p = '(%s)(%s)-(%s)(%s)' % (pa, pn, pa, pn)
_pat = re.compile(p)

def check_match(s):
    m = _pat.search(s)
    if not m:
        return False
    a0, n0, a1, n1 = m.groups()
    if len(a0) != len(a1):
        return False
    if len(n0) > len(n1):
        return False
    return True

撰写回答