PEP 604 – 允许将联合类型写成 X | Y
- 作者:
- Philippe PRADOS <python at prados.fr>,Maggie Moss <maggiebmoss at gmail.com>
- 赞助人:
- Chris Angelico <rosuav at gmail.com>
- BDFL 代表:
- Guido van Rossum <guido at python.org>
- 讨论列表:
- Typing-SIG 列表
- 状态:
- 最终
- 类型:
- 标准跟踪
- 主题:
- 类型提示
- 创建日期:
- 2019 年 8 月 28 日
- Python 版本:
- 3.10
- 历史记录:
- 2019 年 8 月 28 日,2020 年 8 月 5 日
摘要
此 PEP 提出对类型上的 |
运算符进行重载,以允许将 Union[X, Y]
写成 X | Y
,并允许它出现在 isinstance
和 issubclass
调用中。
动机
PEP 484 和 PEP 526 提出了一种通用语法,用于向变量、参数和函数返回值添加类型提示。 PEP 585 提出要 在运行时公开泛型的参数。Mypy [1] 接受一个看起来像这样的语法
annotation: name_type
name_type: NAME (args)?
args: '[' paramslist ']'
paramslist: annotation (',' annotation)* [',']
- 要描述一个析取(联合类型),用户必须使用
Union[X, Y]
。
这种语法的冗长性不利于类型采用。
提案
受 Scala [2] 和 Pike [3] 的启发,此提案添加了运算符 type.__or__()
。使用此新运算符,可以编写 int | str
而不是 Union[int, str]
。除了注释之外,此表达式的结果在 isinstance()
和 issubclass()
中也应该是有效的。
isinstance(5, int | str)
issubclass(bool, int | float)
我们还能够编写 t | None
或 None | t
而不是 Optional[t]
isinstance(None, int | None)
isinstance(42, None | int)
规范
新的联合语法应该被接受用于函数、变量和参数注释。
简化语法
# Instead of
# def f(list: List[Union[int, str]], param: Optional[int]) -> Union[float, str]
def f(list: List[int | str], param: int | None) -> float | str:
pass
f([1, "abc"], None)
# Instead of typing.List[typing.Union[str, int]]
typing.List[str | int]
list[str | int]
# Instead of typing.Dict[str, typing.Union[int, float]]
typing.Dict[str, int | float]
dict[str, int | float]
现有的 typing.Union
和 |
语法应该等价。
int | str == typing.Union[int, str]
typing.Union[int, int] == int
int | int == int
联合中的项目顺序不应该影响相等性。
(int | str) == (str | int)
(int | str | float) == typing.Union[str, float, int]
可选值应该等价于新的联合语法。
None | t == typing.Optional[t]
应该实现一个新的 Union.__repr__() 方法。
str(int | list[str])
# int | list[str]
str(int | int)
# int
isinstance 和 issubclass
只要联合项本身是 isinstance
和 issubclass
的有效参数,新的语法就应该被接受用于对 isinstance
和 issubclass
的调用。
# valid
isinstance("", int | str)
# invalid
isinstance(2, list[int]) # TypeError: isinstance() argument 2 cannot be a parameterized generic
isinstance(1, int | list[int])
# valid
issubclass(bool, int | float)
# invalid
issubclass(bool, bool | list[int])
不兼容的更改
在某些情况下,一些异常不会像预期的那样被引发。
如果元类实现了 __or__
运算符,它将覆盖此运算符。
>>> class M(type):
... def __or__(self, other): return "Hello"
...
>>> class C(metaclass=M): pass
...
>>> C | int
'Hello'
>>> int | C
typing.Union[int, __main__.C]
>>> Union[C, int]
typing.Union[__main__.C, int]
异议和回应
有关讨论的更多详细信息,请参阅下面的链接。
1. 为 Union[type1, type2]
添加一个新的运算符?
优点
- 此语法更易读,并且类似于其他语言(Scala 等)。
- 在运行时,
int|str
可能会在 3.10 中返回一个简单的对象,而不是你需要从导入typing
中获取的所有内容。
缺点
- 添加此运算符引入了
typing
和builtins
之间的依赖关系。 - 破坏了向后移植(因为
typing
可以轻松地进行向后移植,但核心types
却不能)。 - 如果 Python 本身不需要更改,我们仍然需要在 mypy、Pyre、PyCharm、Pytype 以及其他什么东西中实现它(这是一个小的更改,请参阅“参考实现”)。
2. 只更改 PEP 484(类型提示)以接受语法 type1 | type2
?
PEP 563(注释的延迟求值)足以接受此提议,如果我们接受不与注释的动态求值兼容 (eval()
)。
>>> from __future__ import annotations
>>> def foo() -> int | str: pass
...
>>> eval(foo.__annotations__['return'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'type' and 'type'
3. 将 isinstance()
和 issubclass()
扩展为接受 Union
?
isinstance(x, str | int) ==> "is x an instance of str or int"
优点
- 如果允许它们,则实例检查可以使用非常简洁的表示法。
缺点
- 必须将所有
typing
模块迁移到builtin
中。
参考实现
必须实现一个新的内置 Union
类型来保存 t1 | t2
的返回值,并且它必须受 isinstance()
和 issubclass()
支持。此类型可以放在 types
模块中。必须提供 types.Union
和 typing.Union
之间的互操作性。
一旦 Python 语言扩展,mypy [1] 和其他类型检查器将需要更新以接受此新语法。
参考文献
版权
本文档置于公共领域或根据 CC0-1.0-Universal 许可证,以更具许可性的为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0604.rst
上次修改时间:2024-02-16 17:06:07 GMT