仅用于类型提示时是否应该导入类?PEP 560

Hen*_*yKo 2 type-hinting strong-typing pep python-3.7

进口有from __future__ import annotations什么好处?当我理解正确时,我应该在运行时停止不必要的输入导入。

在我的例子HelloWorld中只需要打字。但是使用此代码,输出始终为:

Should this happen? x = World!

当我删除from hello import HelloWorldPyCharm 中的输入帮助时,它不再起作用(我可以理解这一点,因为它不知道从哪里来HelloWorld)。

from __future__ import annotations

from hello import HelloWorld

if __name__ == '__main__':
    def hello(x: str, hello: HelloWorld = None):
        if hello is not None:
            print('hello.data_01 =', hello.data_01)
        print('x =', x)


    hello('World!')
Run Code Online (Sandbox Code Playgroud)

你好.py

from dataclasses import dataclass


@dataclass
class HelloWorld:
    data_01: str
    data_02: str


print("Should this happen?")
Run Code Online (Sandbox Code Playgroud)

所以我的问题是我是否仍然需要from hello import HelloWorld做我能从中得到什么好处from __future__ import annotations

Mic*_*x2a 6

from __future__ import annotations进口有一个核心优势:它使得使用向前引用清洁。

例如,考虑这个(当前损坏的)程序。

# Error! MyClass has not been defined yet in the global scope
def foo(x: MyClass) -> None:
    pass

class MyClass:
    # Error! MyClass is not defined yet, in the class scope
    def return_copy(self) -> MyClass:
        pass
Run Code Online (Sandbox Code Playgroud)

当您尝试在运行时运行该程序时,该程序实际上会崩溃:在实际定义它之前,您已经尝试使用 'MyClass'。为了之前解决这个问题,您必须使用类型注释语法或将每个“MyClass”包装在一个字符串中以创建前向引用

def foo(x: "MyClass") -> None:
    pass

class MyClass:
    def return_copy(self) -> "MyClass":
        pass
Run Code Online (Sandbox Code Playgroud)

虽然这有效,但感觉非常笨拙。类型应该是类型:我们不需要手动将某些类型转换为字符串,只是为了让类型与 Python 运行时很好地配合。

我们可以通过包含from __future__ import annotationsimport来解决这个问题:该行在运行时自动使所有类型成为字符串。这让我们可以编写看起来像第一个示例的代码而不会崩溃:因为每个类型提示在运行时实际上是一个字符串,我们不再引用尚不存在的东西。

而像 mypy 或 Pycharm 这样的类型检查器不会在意:对他们来说,无论 Python 本身选择如何表示类型,类型看起来都是一样的。


需要注意的一件事是,这个导入本身并不能让我们避免导入东西。这样做只是让它更干净。

例如,请考虑以下情况:

from expensive_to_import_module import MyType

def blah(x: MyType) -> None: ...
Run Code Online (Sandbox Code Playgroud)

如果expensive_to_import_module有很多启动逻辑,那可能意味着导入MyType. 一旦程序实际运行,这不会真正产生影响,但它确实会使启动时间变慢。如果您尝试编写短命的命令行风格程序,这会让您感觉特别糟糕:添加类型提示的行为有时会使您的程序在启动时感觉更加迟钝。

我们可以通过创建MyType一个字符串解决这个问题,同时将导入隐藏在一个if TYPE_CHECKING守卫后面,像这样:

from typing import TYPE_CHECKING

# TYPE_CHECKING is False at runtime, but treated as True by type checkers.
# So the Python interpreters won't do the expensive import, but the type checker
# will still understand where MyType came from!
if TYPE_CHECKING:
    from expensive_to_import_module import MyType

# This is a string reference, so we don't attempt to actually use MyType
# at runtime.
def blah(x: "MyType") -> None: ...
Run Code Online (Sandbox Code Playgroud)

这有效,但再次看起来笨重。为什么我们需要在最后一个类型周围添加引号?在annotations未来的进口使得这样做有点平滑的语法:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from expensive_to_import_module import MyType

# Hooray, no more quotes!
def blah(x: MyType) -> None: ...
Run Code Online (Sandbox Code Playgroud)

根据您的观点,这似乎不是一个巨大的胜利。但它确实有助于使使用类型提示更符合人体工程学,使它们感觉更“集成”到 Python 中,并且(一旦默认启用这些提示)消除了 PEP 484 新手容易绊倒的常见绊脚石。