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 样式注释+。
您可以安装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 模式)]- 一个]字符。