类 仅用于类型提示时是否应该导入?政治公众人物 560
Should classes be import when only used for type hints? PEP 560
导入 from __future__ import annotations
有什么好处?当我理解正确时,我应该在运行时停止不必要的输入导入。
在我的示例中,HelloWorld
仅用于键入。但是使用这段代码,输出总是:
Should this happen?
x = World!
当我删除 from hello import HelloWorld
时,PyCharm 中的打字帮助不再有效(我能理解这一点,因为它不理解 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!')
hello.py
from dataclasses import dataclass
@dataclass
class HelloWorld:
data_01: str
data_02: str
print("Should this happen?")
所以我的问题是,如果我还需要做 from hello import HelloWorld
我能从 from __future__ import annotations
中得到什么好处?
from __future__ import annotations
导入有一个核心优势:它使使用 forward references 更干净。
例如,考虑这个(目前已损坏的)程序。
# 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
当您在运行时尝试 运行 该程序实际上 崩溃 :您在它实际定义之前已经尝试使用 'MyClass'。为了之前解决这个问题,您必须使用类型注释语法或将每个 'MyClass' 包装在一个字符串中以创建一个 前向引用 :
def foo(x: "MyClass") -> None:
pass
class MyClass:
def return_copy(self) -> "MyClass":
pass
虽然这有效,但感觉很糟糕。类型应该是类型:我们不需要手动将某些类型转换为字符串,只是为了让类型在 Python 运行时很好地发挥作用。
我们可以通过包含 from __future__ import annotations
导入来解决这个问题:该行自动使 all 在运行时键入一个字符串。这让我们可以编写看起来像第一个示例的代码而不会崩溃:因为每个类型提示在运行时实际上是一个字符串,所以我们不再引用尚不存在的内容。
像 mypy 或 Pycharm 这样的类型检查器不会在意:对他们来说,无论 Python 本身选择如何表示它,类型看起来都是一样的。
需要注意的一点是,这种导入本身并不能让我们避免导入东西。这样做只会使它更干净。
例如,考虑以下内容:
from expensive_to_import_module import MyType
def blah(x: MyType) -> None: ...
如果 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: ...
这行得通,但又显得笨重。为什么我们需要在最后一个类型周围添加引号? annotations
future import 使语法更流畅:
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: ...
根据您的观点,这似乎不是一个巨大的胜利。但它 确实 有助于使类型提示的使用更符合人体工程学,使它们感觉更 "integrated" 进入 Python,并且(一旦这些默认启用)删除了一个常见的PEP 484 的绊脚石新人往往会被绊倒。
导入 from __future__ import annotations
有什么好处?当我理解正确时,我应该在运行时停止不必要的输入导入。
在我的示例中,HelloWorld
仅用于键入。但是使用这段代码,输出总是:
Should this happen?
x = World!
当我删除 from hello import HelloWorld
时,PyCharm 中的打字帮助不再有效(我能理解这一点,因为它不理解 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!')
hello.py
from dataclasses import dataclass
@dataclass
class HelloWorld:
data_01: str
data_02: str
print("Should this happen?")
所以我的问题是,如果我还需要做 from hello import HelloWorld
我能从 from __future__ import annotations
中得到什么好处?
from __future__ import annotations
导入有一个核心优势:它使使用 forward references 更干净。
例如,考虑这个(目前已损坏的)程序。
# 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
当您在运行时尝试 运行 该程序实际上 崩溃 :您在它实际定义之前已经尝试使用 'MyClass'。为了之前解决这个问题,您必须使用类型注释语法或将每个 'MyClass' 包装在一个字符串中以创建一个 前向引用 :
def foo(x: "MyClass") -> None:
pass
class MyClass:
def return_copy(self) -> "MyClass":
pass
虽然这有效,但感觉很糟糕。类型应该是类型:我们不需要手动将某些类型转换为字符串,只是为了让类型在 Python 运行时很好地发挥作用。
我们可以通过包含 from __future__ import annotations
导入来解决这个问题:该行自动使 all 在运行时键入一个字符串。这让我们可以编写看起来像第一个示例的代码而不会崩溃:因为每个类型提示在运行时实际上是一个字符串,所以我们不再引用尚不存在的内容。
像 mypy 或 Pycharm 这样的类型检查器不会在意:对他们来说,无论 Python 本身选择如何表示它,类型看起来都是一样的。
需要注意的一点是,这种导入本身并不能让我们避免导入东西。这样做只会使它更干净。
例如,考虑以下内容:
from expensive_to_import_module import MyType
def blah(x: MyType) -> None: ...
如果 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: ...
这行得通,但又显得笨重。为什么我们需要在最后一个类型周围添加引号? annotations
future import 使语法更流畅:
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: ...
根据您的观点,这似乎不是一个巨大的胜利。但它 确实 有助于使类型提示的使用更符合人体工程学,使它们感觉更 "integrated" 进入 Python,并且(一旦这些默认启用)删除了一个常见的PEP 484 的绊脚石新人往往会被绊倒。