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

sg24os4d  于 2023-01-14  发布在  其他
关注(0)|答案(4)|浏览(98)

我有一个字符串,我试图验证一些正则表达式模式,我希望,因为模式匹配在Python 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')

对于正则表达式1、2和3,我尝试了字符串正则表达式模式,也重新编译对象,但似乎不起作用。
我一直试图在互联网上找到这样的例子,但似乎找不到任何涵盖正则表达式模式匹配与新的python模式匹配。
你知道我该怎么做吗?
谢谢!

k4aesqcs

k4aesqcs1#

更新

我将这个答案压缩到一个python package中,以使匹配像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")

原始答案

正如Patrick Artner在other answer中正确指出的那样,目前还没有正式的方法来实现这一点,希望在未来的Python版本中引入该特性,这样这个问题就可以被淘汰了,直到那时:
PEP634指定结构模式匹配使用==操作符来评估匹配。我们可以覆盖它。

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

现在,您可以执行以下操作:

match regex_in(validated_string):
    case r'\d+':
        print('Digits')
    case r'\s+':
        print('Whitespaces')
    case _:
        print('Something else')
    • 警告#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')

这可以进一步扩展,以提供访问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}')
b4wnujal

b4wnujal2#

清洁溶液

这个问题有一个干净的解决方案,只要把正则表达式从不支持它们的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)

这将输出:

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))

更好的例子

为了便于理解和添加更多案例,可以通过以详细格式编写组合正则表达式来改进代码,还可以通过命名正则表达式子模式来进一步改进代码:

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

可以在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)

与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)

与match/case相比,if-elif-else链速度较慢,因为它运行多个正则表达式匹配,而且没有预编译,而且没有case名称,维护性较差。
因为所有的正则表达式都是独立的,所以我们必须通过重复使用带有walrus操作符的赋值表达式来分别捕获所有的match对象,这与match/case示例中只进行一次赋值相比是很笨拙的。

ct2axkht

ct2axkht3#

不可能使用正则模式通过结构模式匹配进行匹配(此时)。
来自:PEP 0643:结构模式匹配

PEP 634:结构模式匹配

结构模式匹配以模式的match语句和case语句的形式添加,并带有关联的操作。模式序列、Map、原始数据类型以及类示例组成。模式匹配使程序能够从复杂的数据类型中提取信息,在数据结构上进行分支,并根据不同形式的数据采取具体行动。
这里没有给出任何提示,说明在所提供的模式上调用re模块的match / search函数是为了进行匹配。
你可以通过阅读下面的PEP来了解更多关于结构模式匹配背后的原因:

它们还包括关于如何使用它的大量例子。

xdnvmnnf

xdnvmnnf4#

下面的例子基于R. Hettinger的talk,讨论了一种类似于@ahoff的帖子的方法。

    • 鉴于**
import re

class RegexEqual(str):
    def __eq__(self, pattern):
        return bool(re.search(pattern, self))
    • 代码**
def validate(s):
    """A naive string validator."""
    match RegexEqual(s):
        case "\d+":
            return "Number found"
        case "\w+":
            return "Alpha found"
        case _:
            return "Unknown"
    • 演示**
validate("123")
# 'Number found'
validate("hi")
# 'Alpha found'
validate("...")
# 'Unknown'
    • 详细信息**

RegexEqualstr的直接子类,它只是覆盖了==运算符。

RegexEqual("hello") == "h...o"
# True
    • 另请参阅**
  • R. Hettinger的toolkit关于常见match-case解决方法的介绍。

相关问题