我正在查看Forward References上的PEP 484部分,并注意到以下声明:
...该定义可以表示为字符串文字,稍后再解决。
这让我想知道,“以后”什么时候出现?解释器以后不会尝试将其解析为文字,那么怎么办?仅仅是编写了第三方工具可以做到这一点吗?
演示解释器结果的小例子:
class A:
def test(self, a: 'A') -> None:
pass
class B:
def test(self, a: A) -> None:
pass
>>> A().test.__annotations__
{'a': 'A', 'return': None}
>>> B().test.__annotations__
{'a': <class '__main__.A'>, 'return': None}
如果我对函数注释和类型提示的理解是正确的,那么Python并不会在运行时真正对它们进行任何操作来提高性能,但是内省性使用允许严格 >第三方应用程序,例如linter,IDE和静态分析工具(例如mypy
),以利用其可用性。那么这些工具会尝试解决'A'
的类型提示,而不是将其作为工作交给解释器吗?如果是的话,它们如何实现呢?
更新:
通过使用typing
模块,用户代码可以执行以下操作:
>>> typing.get_type_hints(A().test)
{'a': <class '__main__.A'>, 'return': <class 'NoneType'>}
>>> typing.get_type_hints(B().test)
{'a': <class '__main__.A'>, 'return': <class 'NoneType'>}
但是,我的问题是针对Python是否有责任从字符串文字中更新函数的__annotations__
,也就是说在运行时更改时:
>>> A().test.__annotations__
{'a': 'A', 'return': None}
到...
>>> A().test.__annotations__
{'a': <class '__main__.A'>, 'return': None}
如果Python不这么做,那为什么我要用字符串文字作为类型提示而不是自记录代码?第一种形式给用户,用户或第三方工具有什么价值?
答案 0 :(得分:2)
考虑以下代码:
class Foo:
def bar(self) -> Foo:
return Foo()
如果您尝试使用Python运行该程序,则该程序实际上将在运行时崩溃:当解释器看到bar
的定义时,Foo
的定义尚未完成。因此,由于Foo
尚未添加到全局名称空间,因此我们还不能将其用作类型提示。
类似地,考虑以下程序:
class Foo:
def bar(self) -> Bar:
return Bar()
class Bar:
def foo(self) -> Foo:
return Foo()
这个相互依赖的定义也遇到了同样的问题:在评估Foo
时,Bar
尚未被评估,因此解释器抛出异常。
有三个解决方案。第一种是使您的某些类型提示字符串有效地“向前声明”它们:
class Foo:
def bar(self) -> "Foo":
return Foo()
这满足了Python解释器的要求,并且不会破坏像mypy这样的第三方工具:它们可以在解析类型之前删除引号。主要缺点是此语法看起来有些丑陋和笨拙。
第二种解决方案是使用类型注释语法:
class Foo:
def bar(self):
# type: () -> Foo
return Foo()
这与第一个解决方案具有相同的优点和缺点:它满足解释器和工具的要求,但是看起来很笨拙。它还具有其他好处,可以使您的代码与Python 2.7向后兼容。
第三个解决方案仅是Python 3.7+-使用from __future__ import annotations
指令:
from __future__ import annotations
class Foo:
def bar(self) -> Foo:
return Foo()
这将自动使所有注释都表示为字符串。因此,我们可以从第一个解决方案中受益,但不会带来麻烦。
此行为最终将在将来的Python版本中成为默认设置。
结果还表明,自动制作所有注释字符串可以带来一些性能改进。构造List[Dict[str, int]]
之类的类型可能会非常昂贵:它们只是运行时的正则表达式,并且像它们被写为List.__getitem__(Dict.__getitem__((str, int))
一样进行评估。
评估此表达式有些昂贵:我们最终执行两个方法调用,构造一个元组,并构造两个对象。当然,这还没有计入__getitem__
方法本身中发生的任何其他工作-并且这些方法中发生的工作由于不必要而最终变得不平凡。
(简而言之,他们需要构造特殊的对象,以确保不会在运行时以不适当的方式使用List[int]
之类的类型,例如在isinstance
检查等中使用。)