Python是否评估前向引用的类型提示? [英] Does Python evaluate type hinting of a forward reference?

查看:165
本文介绍了Python是否评估前向引用的类型提示?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在查看转发参考并注意到以下声明:

I was looking at the PEP 484 section on Forward References and noticed the statement:

...该定义可以表示为字符串文字,稍后再解决.

...that definition may be expressed as a string literal, to be resolved later.

那让我想知道,以后"是什么时候,又是什么时候?解释器以后不再尝试将其解析为文字,那么怎么办?仅仅是编写了第三方工具可以做到这一点吗?

And that got me wondering, when is "later" and by what? The interpreter doesn't try to resolve it as a literal later, so what does? Is it just if a third party tool is written to do that?

演示解释器结果的小例子:

Small example to demonstrate the interpreter result:

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'的类型提示,而不是将其作为工作交给解释器吗?如果是的话,它们如何实现呢?

If my understanding of function annotations and type hints is correct, Python doesn't really do anything with them at runtime to improve performance, but rather the introspective use allows strictly third party applications such as linters, IDEs and static analysis tools (such as mypy) to take advantage of their availability. So would those tools try to resolve the type hint of 'A' rather than having that be a job given to the interpreter and if so, how do they accomplish this?

更新:

通过使用typing模块,用户代码可以执行以下操作:

By using the typing module, user code can perform the following:

>>> 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__,也就是说在运行时更改时:

However, my question is aimed at whether or not Python has any responsibility in updating the __annotations__ of a function from a string literal, that is to say at runtime change:

>>> A().test.__annotations__
{'a': 'A', 'return': None}

到...

>>> A().test.__annotations__
{'a': <class '__main__.A'>, 'return': None}

如果Python不这么做,那为什么我要用字符串文字作为类型提示而不是自记录代码?第一种形式给用户,用户或第三方工具有什么价值?

If Python doesn't do it, then why would I want a string literal as a type hint other than for self-documented code? What value does the first form give to me, a user or a third party tool?

推荐答案

请考虑以下代码:

class Foo:
    def bar(self) -> Foo:
        return Foo()

如果您尝试使用Python运行该程序,则该程序实际上将在运行时崩溃:当解释器看到bar的定义时,Foo的定义尚未完成.因此,由于Foo尚未添加到全局名称空间中,因此我们还不能将其用作类型提示.

This program will actually crash at runtime if you try running it with Python: when the interpreter sees the definition of bar, the definition of Foo is not yet finished. So, since Foo has not yet been added to the global namespace, we can't use it as a type hint yet.

类似地,考虑以下程序:

Similarly, consider this program:

class Foo:
    def bar(self) -> Bar:
        return Bar()

class Bar:
    def foo(self) -> Foo:
        return Foo()

这个相互依赖的定义也遇到了同样的问题:在评估Foo时,尚未评估Bar,因此解释器会抛出异常.

This mutually dependent definition suffers from the same problem: while we're evaluating Foo, Bar hasn't been evaluated yet so the interpreter throws an exception.

有三个解决方案.首先是使您的某些类型提示字符串有效地向前声明"它们:

There are three solutions to this problem. The first is to make some of your type hints strings, effectively "forward declaring" them:

class Foo:
    def bar(self) -> "Foo":
        return Foo()

这满足了Python解释器的要求,并且不会破坏像mypy这样的第三方工具:它们可以在解析类型之前删除引号.主要缺点是此语法看起来难看且笨拙.

This satisfies the Python interpreter, and won't disrupt third party tools like mypy: they can just remove the quotes before parsing the type. The main disadvantage is that this syntax looks sort of ugly and clunky.

第二种解决方案是使用类型注释语法:

The second solution is to use type comments syntax:

class Foo:
    def bar(self):
        # type: () -> Foo
        return Foo()

这与第一个解决方案具有相同的优点和缺点:它满足解释器和工具的要求,但是看起来很笨拙.它还具有其他好处,可以使您的代码与Python 2.7向后兼容.

This has the same benefits and disadvantages as the first solution: it satisfies the interpreter and tooling, but looks hacky and ugly. It also has the additional benefit that it keeps your code backwards-compatibile with Python 2.7.

第三个解决方案仅是Python 3.7+-使用from __future__ import annotations指令:

The third solution is Python 3.7+ only -- use the from __future__ import annotations directive:

from __future__ import annotations 

class Foo:
    def bar(self) -> Foo:
        return Foo()

这将自动使所有注释都表示为字符串.因此,我们获得了第一个解决方案的好处,但没有丑陋之处.

This will automatically make all annotations be represented as strings. So we get the benefit of the first solution, but without the ugliness.

此行为最终将在将来的Python版本中成为默认设置.

This behavior will eventually become the default in future versions of Python.

结果还表明,自动制作所有注释字符串可以带来一些性能改进.构造List[Dict[str, int]]之类的类型可能会非常昂贵:它们只是运行时的正则表达式,并且就像它们被编写为List.__getitem__(Dict.__getitem__((str, int))一样进行评估.

It also turns out that automatically making all annotations strings can come with some performance improvements. Constructing types like List[Dict[str, int]] can be surprisingly expensive: they're just regular expressions at runtime and evaluated as if they were written as List.__getitem__(Dict.__getitem__((str, int)).

评估此表达式有些昂贵:我们最终执行了两个方法调用,构造了一个元组,并构造了两个对象.当然,这还不包括__getitem__方法本身发生的任何其他工作,并且这些方法中发生的工作由于不必要而最终变得微不足道.

Evaluating this expression is somewhat expensive: we end up performing two method calls, constructing a tuple, and constructing two objects. This isn't counting any additional work that happens in the __getitem__ methods themselves, of course -- and the work that happens in those methods ends up being non-trivial out of necessity.

(简而言之,他们需要构造特殊的对象,以确保像List[int]这样的类型在运行时不会以不适当的方式使用-例如,在isinstance检查等中.)

(In short, they need to construct special objects that ensure types like List[int] can't be used in inappropriate ways at runtime -- e.g. in isinstance checks and the like.)

这篇关于Python是否评估前向引用的类型提示?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆