【发布时间】:2020-07-13 07:58:57
【问题描述】:
PEP 585 -- Type Hinting Generics In Standard Collections 声明在 Python 3.7 和 3.8 下都具有标准 from __future__ import annotations 序言的可用性。值得注意的是:
对于仅限于类型注释的用例,带有
annotationsfuture-import(自 Python 3.7 起可用)的 Python 文件可以参数化标准集合,包括内置函数。
从 Python 3.7 开始,当使用
from __future__ import annotations时,函数和变量注解可以直接参数化标准集合。示例:
from __future__ import annotations
def find(haystack: dict[str, list[int]]) -> int:
...
虽然上面的玩具示例确实在技术上进行了解析,但这就是它所做的全部。在 Python 3.7 或 3.8 下尝试在运行时实际使用参数化的内置集合总是会引发可怕的 TypeError: 'type' object is not subscriptable 异常:
>>> def find(haystack: dict[str, list[int]]) -> int: pass
>>> print(find.__annotations__)
{'haystack': 'dict[str, list[int]]', 'return': 'int'}
>>> eval(find.__annotations__['haystack'])
TypeError: 'type' object is not subscriptable
注意eval() 语句是解析PEP 563-style postponed annotations at runtime 的标准习惯用法。 甚至不要让我开始使用 PEP 563。
你会相信谁:我还是你撒谎的 PEP?
这让我心中虔诚的 Pythonista 望而却步。 PEP 585 repeatedly claims that it preserves runtime usability:
在运行时保留泛型类型可以对可用于 API 生成或运行时类型检查的类型进行自省。这种用法已经在野外存在。
就像今天的
typing模块一样,上一节中列出的参数化泛型类型都在运行时保留了它们的类型参数:
>>> list[str]
list[str]
>>> tuple[int, ...]
tuple[int, ...]
>>> ChainMap[str, list[str]]
collections.ChainMap[str, list[str]]
当然,以上方法在 Python 3.7 或 3.8 下都不起作用——无论from __future__ import annotations 是否启用:
>>> list[str]
TypeError: 'type' object is not subscriptable
>>> tuple[int, ...]
TypeError: 'type' object is not subscriptable
>>> ChainMap[str, list[str]]
TypeError: 'type' object is not subscriptable
所以 PEP 585 公然打破了在运行时自省泛型类型的狂野和所有现有尝试——尤其是来自运行时类型检查器。整个“泛型参数在运行时可用”部分是一个字谜。
我是否遗漏了一些非常明显的东西,或者参数化的内置集合是它们表面上看起来的毒丸?由于在 Python 3.7 和 3.8 下在运行时评估这些集合会无条件地引发异常,因此它们在运行时不可用 - 不仅使它们毫无用处,而且对类型自省和运行时类型检查的广泛用例尤其是直接有害.
在岩石和硬 PEP 之间
任何带有参数化内置集合的代码库类型提示都将与 Python 3.7 和 3.8 下的运行时类型检查器根本不兼容。代码库更喜欢运行时而不是静态类型检查,同时保持与 Python
除了那也是不可行的。为什么?因为PEP 585 deprecates the entire hierarchy of typing pseudo-containers:
不推荐从
typing导入这些 [例如,typing.Tuple、typing.List、typing.Dict]。 由于 PEP 563 和旨在最大限度地减少typing的运行时影响,此弃用不会生成DeprecationWarnings。相反,当被检查程序的目标版本被通知为 Python 3.9 或更高版本时,类型检查器可能会警告这种不推荐使用的用法。建议允许在整个项目范围内消除这些警告。
在 Python 3.9.0 发布 5 年后发布的第一个 Python 版本中,已弃用的功能将从
typing模块中删除。
以typing.Tuple[int] 为例。到 2025 年(或此后不久),typing.Tuple 和 typing.Tuple[int] 将消失。但是tuple 在 Python 3.7 和 3.8 下不能安全地参数化,因为这样做会使您的项目与内省类型的任何东西不兼容。所以tuple[int] 也不是一个可行的选择。
因此没有向前和向后兼容的选项。相反,要么:
-
禁止类型自省(并因此禁止运行时类型检查)完全通过只首选内置容器(例如,
tuple[int])而不是typing伪容器(例如,typing.Tuple[int])或嗯>... -
支持类型自省(以及运行时类型检查),方法是:
- 在 2025 年之前,优先使用
typing伪容器而不是内置容器。届时,有问题的项目和该项目的所有下游项目都需要进行如下重构:- 放弃对 Python 3.7 和 3.8 的支持。
- 用内置容器替换所有
typing伪容器。
- 通过首选内置容器而不是
typing伪容器,立即放弃对 Python 3.7 和 3.8 的支持。这有一个令人讨厌的缺点,即需要当前不稳定的 Python 解释器,但是……从技术上讲,这是一种选择。不知何故。
- 在 2025 年之前,优先使用
在 2020 年,没有什么好的选择——只有一系列越来越可怕的恶毒中的小恶魔。人们希望 PEP 作者能够在运行时实际测试他们的实现。然而,我们在这里,在理论制造的反 API 的热气腾腾的粪坑中漂流,没有桨。 欢迎使用 Python。
但这还不是全部
技术上还有第三种方式。它甚至更多令人反感——但它应该在技术上有效。我总是说,一个糟糕的理论设计值得另一个!
由于 PEP 563 驱动的延迟注释只是字符串,类型自省可以巧妙地对每个被自省的类型运行基于正则表达式的替换。对于作为延迟注释的每种类型,将该注释字符串中引用参数化内置容器(例如list[str])的每个子字符串全局替换为引用参数化typing伪容器(例如List[str])的相应子字符串。
结果?与 Python 3.7 和 3.8 兼容的延迟注释字符串可安全评估到 2025 年,届时内部替换(以及对 Python 3.7 和 3.8 的支持)可能会被悄悄放弃。
对于星星来说,这完全是一个疯狂的可笑速度组合,但是......会可能会奏效。当然,核心问题是人们不应该仅仅为了遵守核心官方 PEP 而需要疯狂的骇客。但在那个技术问题之下还有一个更深层次的文化问题。没有人——无论是 PEP 585 的作者还是任何审查 PEP 585 的评论员——在弃用现有的经过良好测试但实际工作的功能之前,实际测试了他们提出的新假设功能。
核心官方 PEP 应该工作开箱即用。越来越多,他们没有。这应该引起每个人的关注。
【问题讨论】:
-
这里可能有一个合理的问题,但现在它看起来更像是对 PEP 质量的咆哮。请大幅削减。
-
Python 注释系统的整个设计几乎不关心运行时支持。他们一直到 Python 3.8 才引入
typing.get_args。如果运行时支持在优先级列表中的任何位置,那么自第一个typing版本以来就会存在这种情况。
标签: python python-3.x python-typing pep585