正则表达式匹配“|” 联合类型的分隔值

rv.*_*tch 5 python regex annotations python-3.x python-typing

我正在尝试匹配类型注释,例如int | str,并使用正则表达式替换将它们替换为 string Union[int, str]

期望的替换(之前和之后):

  • str|int|bool->Union[str,int,bool]
  • Optional[int|tuple[str|int]]->Optional[Union[int,tuple[Union[str,int]]]]
  • dict[str | int, list[B | C | Optional[D]]]->dict[Union[str,int], list[Union[B,C,Optional[D]]]]

到目前为止我想出的正则表达式如下:

r"\w*(?:\[|,|^)[\t ]*((?'type'[a-zA-Z0-9_.\[\]]+)(?:[\t ]*\|[\t ]*(?&type))+)(?:\]|,|$)"
Run Code Online (Sandbox Code Playgroud)

您可以在 Regex Demo 上尝试一下。它并没有真正按照我想要的方式工作。到目前为止我注意到的问题:

  • 到目前为止,它似乎无法处理嵌套的 Union 条件。例如,int | tuple[str|int] | bool似乎会导致一场匹配,而不是两次匹配(包括内部 Union 条件)。

  • 正则表达式似乎]最后消耗了不必要的东西。

  • 可能是最重要的一个,但我注意到 Python 中的模块似乎不支持正则表达式子例程re是我想到使用它的地方。

附加信息

这主要是为了支持Python 3.7+ 的PEP 604语法,该语法需要前向声明(例如声明为字符串)注释才能支持,否则内置类型不支持该|运算符。

这是我想出的示例代码:

r"\w*(?:\[|,|^)[\t ]*((?'type'[a-zA-Z0-9_.\[\]]+)(?:[\t ]*\|[\t ]*(?&type))+)(?:\]|,|$)"
Run Code Online (Sandbox Code Playgroud)

对于 3.10 之前的 Python 版本,我使用__future__import 来避免出现以下错误:

TypeError: unsupported operand type(s) for |: 'type' and 'type'
Run Code Online (Sandbox Code Playgroud)

这实际上将所有注释转换为字符串,如下所示:

>>> A.__annotations__
{'field_1': 'str | int | bool', 'field_2': 'int | tuple[str | int] | bool', 'field_3': 'Decimal | datetime.date | str', 'field_4': 'str | Optional[int]', 'field_5': 'Optional[int | str]', 'field_6': 'dict[str | int, list[B | C | Optional[D]]]'}
Run Code Online (Sandbox Code Playgroud)

但在代码中(比如在另一个模块中),我想评估 A 中的注释。这在 Python 3.10 中有效,但在 Python 3.7+ 中失败,即使导入__future__支持前向声明注释。

from __future__ import annotations

import datetime
from decimal import Decimal
from typing import Optional


class A:
    field_1: str|int|bool
    field_2: int  |  tuple[str|int]  |  bool
    field_3: Decimal|datetime.date|str
    field_4: str|Optional[int]
    field_5: Optional[int|str]
    field_6: dict[str | int, list[B | C | Optional[D]]]

class B: ...
class C: ...
class D: ...
Run Code Online (Sandbox Code Playgroud)

似乎实现这项工作的最佳方法是将所有出现的int | str(例如)替换为Union[int, str],然后包含在用于评估注释的typing.Union附加内容中localns,然后应该可以评估 Python 3.7 的 PEP 604 样式注释+。

Wik*_*żew 1

您可以安装PyPiregex模块(因为re不支持递归)并使用

import regex
text = "str|int|bool\nOptional[int|tuple[str|int]]\ndict[str | int, list[B | C | Optional[D]]]"
rx = r"(\w+\[)(\w+(\[(?:[^][|]++|(?3))*])?(?:\s*\|\s*\w+(\[(?:[^][|]++|(?4))*])?)+)]"
n = 1
res = text
while n != 0:
    res, n = regex.subn(rx, lambda x: "{}Union[{}]]".format(x.group(1), regex.sub(r'\s*\|\s*', ',', x.group(2))), res) 

print( regex.sub(r'\w+(?:\s*\|\s*\w+)+', lambda z: "Union[{}]".format(regex.sub(r'\s*\|\s*', ',', z.group())), res) )
Run Code Online (Sandbox Code Playgroud)

输出:

Union[str,int,bool]
Optional[Union[int,tuple[Union[str,int]]]]
dict[Union[str,int], list[Union[B,C,Optional[D]]]]
Run Code Online (Sandbox Code Playgroud)

请参阅Python 演示

一个正则表达式查找所有WORD[...]包含管道字符和其他WORD字符或WORD[...]内部不包含管道字符的类型。

\w+(?:\s*\|\s*\w+)+ 则表达式匹配 2 个或多个用竖线和可选空格分隔的单词。

第一个图案详细信息:

  • (\w+\[)- 第 1 组(这将在替换开始时保持原样):一个或多个单词字符,然后是一个[字符
  • (\w+(\[(?:[^][|]++|(?3))*])?(?:\s*\|\s*\w+(\[(?:[^][|]++|(?4))*])?)+)- 第 2 组(将放入其中Union[...],所有\s*\|\s*图案替换为,):
    • \w+- 一个或多个单词字符
    • (\[(?:[^][|]++|(?3))*])?- 匹配一个[字符的可选组 3,后跟零个或多次出现的一个或多个[字符]或整个组 3 递归(因此,它匹配嵌套括号),然后是一个]字符
    • (?:\s*\|\s*\w+(\[(?:[^][|]++|(?4))*])?)+- 出现一次或多次(因此匹配项至少包含一个要替换为 的管道字符,):
      • \s*\|\s*- 用零个或多个空格括起来的管道字符
      • \w+- 一个或多个单词字符
      • (\[(?:[^][|]++|(?4))*])?- 可选的组 4(与组 3 匹配相同的内容,注意(?4)子例程重复组 4 模式)
  • ]- 一个]字符。