使用正则表达式进行结构模式匹配

Kar*_*ngh 22 python regex python-3.10 structural-pattern-matching

我有一个字符串,我正在尝试针对一些正则表达式模式进行验证,并且我希望由于模式匹配在 3.10 中可用,我可能能够使用它而不是创建 if-else 块。

考虑一个字符串“validateString”,其可能值为 1021102、1.25.32、string021。

我尝试的代码如下所示。

match validateString:
    case regex1:
        print('Matched regex1')
    case regex2:
        print('Matched regex2')
    case regex3:
        print('Matched regex3')
Run Code Online (Sandbox Code Playgroud)

对于正则表达式 1、2 和 3,我尝试过字符串正则表达式模式以及 re.compile 对象,但它似乎不起作用。

我一直在尝试在互联网上找到这方面的示例,但似乎找不到任何涵盖正则表达式模式匹配与新的 python 模式匹配的示例。

关于如何让它发挥作用有什么想法吗?

谢谢!

aho*_*off 22

更新

我将这个答案压缩到一个python 包中,以使匹配变得简单pip install regex-spm

import regex_spm

match regex_spm.fullmatch_in("abracadabra"):
  case r"\d+": print("It's all digits")
  case r"\D+": print("There are no digits in the search string")
  case _: print("It's something else")
Run Code Online (Sandbox Code Playgroud)

原答案

正如帕特里克·阿特纳(Patrick Artner)在另一个答案中正确指出的那样,目前没有官方方法可以做到这一点。希望该功能将在未来的 Python 版本中引入,并且这个问题可以被退休。直到那时:

PEP 634指定结构模式匹配使用==运算符来评估匹配。我们可以覆盖它。

import re
from dataclasses import dataclass

# noinspection PyPep8Naming
@dataclass
class regex_in:
    string: str

    def __eq__(self, other: str | re.Pattern):
        if isinstance(other, str):
            other = re.compile(other)
        assert isinstance(other, re.Pattern)
        # TODO extend for search and match variants
        return other.fullmatch(self.string) is not None
Run Code Online (Sandbox Code Playgroud)

现在你可以做类似的事情:

match regex_in(validated_string):
    case r'\d+':
        print('Digits')
    case r'\s+':
        print('Whitespaces')
    case _:
        print('Something else')
Run Code Online (Sandbox Code Playgroud)

注意事项 #1是你不能re.compile直接将 'd 模式传递给 case,因为 Python 想要根据类进行匹配。您必须先将模式保存在某处。

注意事项 #2是,您实际上也不能使用局部变量,因为 Python 会将其解释为用于捕获匹配主题的名称。您需要使用点名称,例如将模式放入类或枚举中:

class MyPatterns:
    DIGITS = re.compile('\d+')

match regex_in(validated_string):
    case MyPatterns.DIGITS:
        print('This works, it\'s all digits')
Run Code Online (Sandbox Code Playgroud)

团体

这可以进一步扩展以提供访问re.Match对象和组的简单方法。

# noinspection PyPep8Naming
@dataclass
class regex_in:
    string: str
    match: re.Match = None

    def __eq__(self, other: str | re.Pattern):
        if isinstance(other, str):
            other = re.compile(other)
        assert isinstance(other, re.Pattern)
        # TODO extend for search and match variants
        self.match = other.fullmatch(self.string)
        return self.match is not None

    def __getitem__(self, group):
        return self.match[group]

# Note the `as m` in in the case specification
match regex_in(validated_string):
    case r'\d(\d)' as m:
        print(f'The second digit is {m[1]}')
        print(f'The whole match is {m.match}')
Run Code Online (Sandbox Code Playgroud)


Ray*_*ger 17

清洁溶液

这个问题有一个干净的解决方案。只需将正则表达式从不支持它们的 case 子句中提升到支持任何 Python 对象的 match 子句中即可。

组合的正则表达式还可以为您提供比进行一系列单独的正则表达式测试更高的效率。此外,可以预编译正则表达式,以在匹配过程中实现最大效率。

例子

这是一个简单标记器的示例:

pattern = re.compile(r'(\d+\.\d+)|(\d+)|(\w+)|(".*)"')
Token = namedtuple('Token', ('kind', 'value', 'position'))
env = {'x': 'hello', 'y': 10}

for s in ['123', '123.45', 'x', 'y', '"goodbye"']:
    mo = pattern.fullmatch(s)
    match mo.lastindex:
        case 1:
            tok = Token('NUM', float(s), mo.span())
        case 2:
            tok = Token('NUM', int(s), mo.span())
        case 3:
            tok = Token('VAR', env[s], mo.span())
        case 4:
            tok = Token('TEXT', s[1:-1], mo.span())
        case _:
            raise ValueError(f'Unknown pattern for {s!r}')
    print(tok) 
Run Code Online (Sandbox Code Playgroud)

这输出:

Token(kind='NUM', value=123, position=(0, 3))
Token(kind='NUM', value=123.45, position=(0, 6))
Token(kind='VAR', value='hello', position=(0, 1))
Token(kind='VAR', value=10, position=(0, 1))
Token(kind='TEXT', value='goodbye', position=(0, 9))
Run Code Online (Sandbox Code Playgroud)

更好的例子

可以通过以详细格式编写组合正则表达式来改进代码,以提高可理解性并易于添加更多案例。可以通过命名正则表达式子模式来进一步改进:

pattern = re.compile(r"""(?x)
    (?P<float>\d+\.\d+) |
    (?P<int>\d+) |
    (?P<variable>\w+) |
    (?P<string>".*")
""")
Run Code Online (Sandbox Code Playgroud)

可以在 match/case 语句中使用,如下所示:

for s in ['123', '123.45', 'x', 'y', '"goodbye"']:
    mo = pattern.fullmatch(s)
    match mo.lastgroup:
        case 'float':
            tok = Token('NUM', float(s), mo.span())
        case 'int':
            tok = Token('NUM', int(s), mo.span())
        case 'variable':
            tok = Token('VAR', env[s], mo.span())
        case 'string':
            tok = Token('TEXT', s[1:-1], mo.span())
        case _:
            raise ValueError(f'Unknown pattern for {s!r}')
    print(tok)
Run Code Online (Sandbox Code Playgroud)

与 if/elif/else 的比较

以下是使用 if-elif-else 链编写的等效代码:

for s in ['123', '123.45', 'x', 'y', '"goodbye"']:
    if (mo := re.fullmatch('\d+\.\d+', s)):
        tok = Token('NUM', float(s), mo.span())
    elif (mo := re.fullmatch('\d+', s)):
        tok = Token('NUM', int(s), mo.span())
    elif (mo := re.fullmatch('\w+', s)):
        tok = Token('VAR', env[s], mo.span())
    elif (mo := re.fullmatch('".*"', s)):
        tok = Token('TEXT', s[1:-1], mo.span())
    else:
        raise ValueError(f'Unknown pattern for {s!r}')
    print(tok)
Run Code Online (Sandbox Code Playgroud)

与 match/case 相比,if-elif-else 链速度较慢,因为它运行多个正则表达式匹配,并且没有预编译。此外,如果没有案例名称,它的可维护性就会降低。

因为所有正则表达式都是独立的,所以我们必须通过海象运算符重复使用赋值表达式来单独捕获所有匹配对象。与我们只进行单个分配的匹配/案例示例相比,这很尴尬。


pyl*_*ang 8

以下示例基于 R. Hettinger 的演讲,讨论了与 @ahoff 的帖子类似的方法。

给定

import re


class RegexEqual(str):
    def __eq__(self, pattern):
        return bool(re.search(pattern, self))
Run Code Online (Sandbox Code Playgroud)

代码

import re


class RegexEqual(str):
    def __eq__(self, pattern):
        return bool(re.search(pattern, self))
Run Code Online (Sandbox Code Playgroud)

演示

def validate(s):
    """A naive string validator."""
    match RegexEqual(s):
        case "\d+":
            return "Number found"
        case "\w+":
            return "Letter found"
        case _:
            return "Unknown"
Run Code Online (Sandbox Code Playgroud)

细节

RegexEqual是它的直接子类,str它只是覆盖该==运算符。

validate("123")
# 'Number found'
validate("hi")
# 'Letter found'
validate("...")
# 'Unknown'
Run Code Online (Sandbox Code Playgroud)

也可以看看

  • R. Hettinger关于常见解决方法的工具包match-case


Pat*_*ner 2

无法使用正则表达式模式通过结构模式匹配进行匹配(此时)。

来自:PEP0643:结构模式匹配

PEP 634:结构模式匹配
结构模式匹配以 match 语句和具有关联操作的模式的 case 语句的形式添加。模式序列、映射、原始数据类型以及类实例组成。模式匹配使程序能够从复杂的数据类型中提取信息,在数据结构上进行分支,并根据不同形式的数据应用特定的操作。(强调我的

其中没有任何提示表明re在提供的模式上调用模块的匹配/搜索功能旨在用于匹配。


您可以通过阅读实际 PEP 来了解有关结构模式匹配背后的推理的更多信息:

它们还包含有关如何使用它的大量示例。