当前位置: 首页 > 知识库问答 >
问题:

使用regex进行结构模式匹配

齐鸿光
2023-03-14

我有一个字符串,我正试图根据几个regex模式验证它,我希望由于模式匹配在3.10中可用,我可以使用它来代替创建if-else块。

考虑一个字符串'validateString',其可能的值1021102,1.25.32string021。

我尝试的代码如下所示。

match validateString:
    case regex1:
        print('Matched regex1')
    case regex2:
        print('Matched regex2')
    case regex3:
        print('Matched regex3')

对于正则表达式1、2和3,我尝试了字符串正则表达式模式,还重新设置了。编译对象,但它似乎不起作用。

我一直试图在互联网上找到这方面的例子,但似乎找不到任何包含regex模式匹配和新的python模式匹配的例子。

有什么办法能让它工作吗?

谢谢

共有3个答案

华修永
2023-03-14

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

发件人:PEP0643:结构模式匹配

PEP 634:结构模式匹配
结构模式匹配以模式的匹配语句和case语句以及相关操作的形式添加。模式包括序列、映射、基本数据类型以及类实例。模式匹配使程序能够从复杂的数据类型中提取信息,对数据结构进行分支,并基于不同形式的数据应用特定的操作。(重点矿山)

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

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

  • PEP 634——结构模式匹配:规范

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

袁山
2023-03-14

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

与使用一系列单独的regex测试相比,组合的regex还将为您提供更好的效率。此外,可以在匹配过程中预编译regex以获得最大效率。

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

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

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

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

可以在这样的匹配/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-ered-chain链编写的等效代码:

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)

与匹配/大小写相比,if-elif-else链速度较慢,因为它运行多个regex匹配,并且没有预编译。此外,如果没有案例名称,它的可维护性也较差。

因为所有正则表达式都是独立的,所以我们必须使用walrus操作符重复使用赋值表达式来分别捕获所有匹配对象。这与我们只做一个分配的匹配/案例示例相比很尴尬。

朱伯寅
2023-03-14

正如帕特里克·阿特纳(PatrickArtner)在另一个答案中正确指出的那样,目前还没有官方的方法来做到这一点。希望该特性将在未来的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

现在你可以做这样的事情:

match regex_in(validated_string):
    case r'\d+':
        print('Digits')
    case r'\s+':
        print('Whitespaces')
    case _:
        print('Something else')

警告#1是您不能将re.compile'd模式直接传递给案例,因为Python希望基于类进行匹配。您必须先将模式保存在某个地方。

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

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

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

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

# 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}')
 类似资料:
  • 问题内容: 我已经在线阅读了文档和各种教程,但是我对regex在Java中的工作方式仍然感到困惑。我正在尝试做的是创建一个接受字符串类型参数的函数。然后,我想检查传递的字符串是否包含MDCLXVIivxlcdm以外的任何字符。因此,例如,字符串“ XMLVID​​”应返回false,而“ ABXMLVA”应返回true。 当我通过时,“ XMLIVD”,“ ABXMLVA”和“ XMLABCIX”

  • 我无法运行此代码: 我在Python中找不到匹配关键字。 我在这里找到的:https://www.python.org/dev/peps/pep-0622/#the-match语句 有什么想法吗?

  • 我一直在将if-elif链转换为结构模式匹配,但在反向测试中遇到了困难。 很容易生成与任何支持的模式(文本、类、映射、序列等)匹配的案例。我如何证明一个否定的匹配? 例如,当对象的类型不匹配时,我需要强制它:

  • 字面量 变量 占位符

  • 或者更好的方法是遍历它,为参数的每个索引获取TRUE或FALSE标志 我只知道如何使用matcher.find()进行循环,如果有任何帮助,我将不胜感激