python - 正则表达式匹配“|” 联合类型的分隔值
问题描述
我正在尝试匹配类型注释,例如int | str
,并使用正则表达式替换将它们替换为字符串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))+)(?:\]|,|$)"
您可以在 Regex Demo 上试用。它并没有真正按照我想要的方式工作。到目前为止我注意到的问题:
到目前为止,它似乎还没有处理嵌套的联合条件。例如,
int | tuple[str|int] | bool
似乎导致一个匹配,而不是两个匹配(包括内部联合条件)。正则表达式最后似乎消耗了不必要
]
的东西。re
可能是最重要的一个,但我注意到Python 中的模块似乎不支持正则表达式子例程。这是我想到使用它的地方。
附加信息
这主要是为了支持 Python 3.7+ 的PEP 604语法,这需要支持注释前向声明(例如声明为字符串),否则内置类型不支持该|
运算符。
这是我想出的示例代码:
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: ...
对于 3.10 之前的 Python 版本,我使用__future__
导入来避免以下错误:
TypeError: unsupported operand type(s) for |: 'type' and 'type'
这实际上将所有注释转换为字符串,如下所示:
>>> 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]]]'}
但是在代码中(比如在另一个模块中),我想评估 A 中的注释。这在 Python 3.10 中有效,但在 Python 3.7+ 中失败,即使__future__
导入支持前向声明的注释。
>>> from typing import get_type_hints
>>> hints = get_type_hints(A)
Traceback (most recent call last):
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'type' and 'type'
似乎完成这项工作的最佳方法是将所有出现的int | str
(例如)替换为Union[int, str]
,然后typing.Union
包含在localns
用于评估注释的附加内容中,然后应该可以评估 Python 3.7 的 PEP 604 样式注释+。
解决方案
您可以安装 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) )
输出:
Union[str,int,bool]
Optional[Union[int,tuple[Union[str,int]]]]
dict[Union[str,int], list[Union[B,C,Optional[D]]]]
请参阅Python 演示。
第一个正则表达式查找WORD[...]
包含管道字符和其他WORD
s 或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 组模式)
]
- 一个]
字符。
推荐阅读
- mysql - 我受到 1 行影响,1 个警告,但我插入了正确的值
- php - PHPUnit 不能模拟类
- knn - “解决缺失数据”如何帮助 KNN 更好地发挥作用?
- php - 在 $content 母版页上显示 PHP 页面
- python-3.x - 如何计算熊猫数据框中2个字符串列之间的差异
- omnet++ - 我需要澄清 OMNET 中的 sendDirect() 方法
- ios - swift:在 ViewController 中返回后显示动画
- django - 在 django 中的每个模板标签后禁止换行
- ruby - 很难在 Mac 上安装 Ruby
- java - Unable to identify extjs date field with partial ID