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] 和其他类型检查器需要更新以接受这种新语法。
- cpython 的建议实现 在此。
- mypy 的建议实现 在此。
参考资料
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0604.rst