PEP 585 在 Python 3.7 和 3.8 下在运行时不可用吗?

Cec*_*rry 13 python python-3.x python-typing pep585

PEP 585 —— 标准集合中的类型提示泛型声称在 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:
    ...
Run Code Online (Sandbox Code Playgroud)

虽然上面的玩具示例技术上进行了解析,但仅此而已。在 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
Run Code Online (Sandbox Code Playgroud)

请注意,该eval()语句是在运行时解析PEP 563 样式延迟注释的标准习惯用法。甚至不要让我开始使用 PEP 563。

你会相信谁:我还是你说谎的 PEP?

这让我心中虔诚的 Pythonista 望而却步。PEP 585 一再声称它保留了运行时可用性

在运行时保留泛型类型可以对可用于 API 生成或运行时类型检查的类型进行自省。这种用法已经存在于野外。

就像typing今天的模块一样,上一节中列出的参数化泛型类型都在运行时保留了它们的类型参数:

>>> list[str]
list[str]
>>> tuple[int, ...]
tuple[int, ...]
>>> ChainMap[str, list[str]]
collections.ChainMap[str, list[str]]
Run Code Online (Sandbox Code Playgroud)

当然,以上都不适用于 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
Run Code Online (Sandbox Code Playgroud)

因此,PEP 585 公然打破了在运行时自省泛型类型的野性和所有现有尝试——尤其是来自运行时类型检查器的尝试。整个“泛型参数在运行时可用”部分是一个把戏。

我是否遗漏了一些非常明显的东西,或者参数化的内置集合是它们表面上看起来的毒丸?由于在 Python 3.7 和 3.8 下在运行时评估这些集合会无条件地引发异常,因此它们在运行时无法使用 - 使它们不仅无用,而且对类型自省的广泛用例,尤其是运行时类型检查的广泛用例直接有害。

在岩石和坚硬的 PEP 之间

任何带有参数化内置集合的代码库类型提示都将与 Python 3.7 和 3.8 下的运行时类型检查器根本不兼容。代码库更喜欢运行时而不是静态类型检查,同时保持与 Python < 3.9(在撰写本文时甚至尚未正式发布)的向后兼容性,因此别无选择,只能完全避免参数化的内置集合。

除了这也是不可行的。为什么?因为PEP 585 弃用了typing伪容器的整个层次结构

不推荐从其中导入那些 [例如, typing.Tuple, typing.List, typing.Dict] typing由于 PEP 563 和旨在最小化 运行时影响的意图typing,这种弃用不会生成DeprecationWarnings. 相反,当被检查程序的目标版本被告知为 Python 3.9 或更新版本时,类型检查器可能会警告此类弃用用法。建议允许在项目范围内消除这些警告。

typingPython 3.9.0 发布 5 年后发布的第一个 Python 版本中,已弃用的功能将从模块中删除。

例如typing.Tuple[int],考虑。到 2025 年(或之后不久),typing.Tuple并因此typing.Tuple[int]消失。但是tuple在 Python 3.7 和 3.8 下不能安全地参数化,因为这样做会使您的项目与任何内省类型不兼容。所以tuple[int]也不是一个可行的选择。

所以没有向前和向后兼容的选项。相反,要么:

  • 完全禁止类型自省(以及运行时类型检查),仅通过更喜欢内置容器(例如tuple[int])而不是typing伪容器(例如typing.Tuple[int]...
  • 通过以下任一方式支持类型自省(以及运行时类型检查):
    • 宁愿typing伪容器内置容器中,直到2025年。当时,无论是有问题的项目该项目的所有下游项目将需要重构如下:
      • 删除 Python 3.7 和 3.8 支持。
      • typing用内置容器替换所有伪容器。
    • 通过优先使用内置容器而typing不是伪容器,立即放弃对 Python 3.7 和 3.8 的支持。这有一个令人讨厌的缺点,即需要当前不稳定的 Python 解释器,但是……从技术上讲,这是一种选择。不知何故。

在 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 的任何评论员——在弃用实际有效的现有经过充分测试的功能之前,实际上测试了他们新的假设性提议功能。

核心官方政治公众人物应该只是工作的开箱即用。越来越多,他们不这样做。这应该与每个人有关。

Mic*_*x2a 2

我不确定您为什么要在 StackOverflow 上发布此内容?如果您有关于 PEP 的反馈,我认为您最好将其发布到 python-dev 或 Typing-sig 邮件列表上。

例如,您也许可以尝试争论:

  1. 我们应该在 2025 年之后删除伪容器。
  2. typing_extensions应更新该模块以提供填充程序listdict复制将在 Python 3.9 中添加的运行时功能。
  3. 最好将伪容器替换为typing内置函数的别名,而不是删除它们。(我怀疑这很可能是最终发生的事情:例如,请参阅Typing-sig 中的讨论

...等等。我相信你能想到更多的想法,这些只是我现在想到的。

无论如何,我认为typing在保持向后兼容性的同时处理伪容器应该很容易被删除。例如,您可以尝试:

  1. 编写一个工具来自动更新您的代码(假设该工具尚未由某人创建)。

  2. 猴子修补typing以添加丢失的内容。

  3. 提前切换到使用您自己的typing垫片,其执行如下操作:

    from typing import *
    if sys.version_info >= (3, blah):
        List = list
        # etc
    
    Run Code Online (Sandbox Code Playgroud)

诚然,这些方法也并非完全干净,但它们确实可以让您实现确保完全向后兼容性的目标。

“禁止类型自省并尽早切换”方法在实践中也可能相当合理:我怀疑相当大比例的代码库专门使用类型提示进行静态类型分析。

但更广泛地说,如果您想要对类型提示进行更无缝的运行时内省,我建议您订阅 Typing-sig 或 python-dev,并在提出与类型相关的 PEP 时提供反馈。

到目前为止,我认为没有人足够关心类型提示的运行时内省,除了确保继续为它们提供基线支持之外,还没有做任何事情。如果您对现状不满意,您应该尝试挺身而出并支持您认为需要做出的任何改变。

毕竟,Python 是一个志愿者驱动的项目。因此,如果你想改变 Python 的某些东西,最好的方法就是自愿贡献你的时间和精力,而不是等待别人来代替你这样做。

  • 好吧,*那*是一个令人惊讶的敌对答复。我不会“等待其他人”代表我“改变 Python 的某些内容”。我试图理解为什么宣传为有效的 PEP 实际上无法像宣传的那样工作(或根本无法工作)——以及我在这一点上是否可能是错误的。显然,我不是。我也不同意类型提示的运行时内省“仍然存在基线支持”。不存在这样的支持。“typing”模块及其关联模块公开反对运行时自省(例如,故意禁止“isinstance”和“issubclass”检查)。 (4认同)