mypy如何使用typing.TYPE_CHECKING解决循环导入注解问题?
How does mypy use typing.TYPE_CHECKING to resolve the circular import annotation problem?
我的包结构如下:
/prog
-- /ui
---- /menus
------ __init__.py
------ main_menu.py
------ file_menu.py
-- __init__.py
__init__.py
prog.py
这些是我的 import/classes 声明:
prog.py
:
from prog.ui.menus import MainMenu
/prog/ui/menus/__init__.py
:
from prog.ui.menus.file_menu import FileMenu
from prog.ui.menus.main_menu import MainMenu
main_menu.py
:
import tkinter as tk
from prog.ui.menus import FileMenu
class MainMenu(tk.Menu):
def __init__(self, master: tk.Tk, **kwargs):
super().__init__(master, **kwargs)
self.add_cascade(label='File', menu=FileMenu(self, tearoff=False))
[...]
file_menu.py
:
import tkinter as tk
from prog.ui.menus import MainMenu
class FileMenu(tk.Menu):
def __init__(self, master: MainMenu, **kwargs):
super().__init__(master, **kwargs)
self.add_command(label='Settings')
[...]
这会导致序列中的循环导入问题:
prog.py
-> __init__.py
-> main_menu.py
-> file_menu.py
-> main_menu.py
-> [...]
根据多次搜索,建议将导入更新为:
file_menu.py
import tkinter as tk
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from prog.ui.menus import MainMenu
class FileMenu(tk.Menu):
def __init__(self, master: 'MainMenu', **kwargs):
super().__init__(master, **kwargs)
self.add_command(label='Settings')
[...]
我已经阅读了 TYPE_CHECKING docs and the mypy docs 的用法,但我不了解如何使用此条件解决循环。是的,在运行时它可以工作,因为它的计算结果为 False
所以这是一个“操作分辨率”,但是它在类型检查期间如何不重新出现:
The TYPE_CHECKING constant defined by the typing module is False at runtime but True while type checking.
我对 mypy 了解不多,因此我看不出一旦条件评估为 True
问题就不会再次出现。 “运行时”和“类型检查”之间有什么不同? “类型检查”的过程是否意味着代码没有被执行?
备注:
这不是循环导入依赖问题,因此不需要依赖注入
这完全是静态分析的类型提示引起的循环
我知道以下导入选项(效果很好):
将from [...] import [...]
替换为import [...]
在 MainMenu.__init__
进行进口,让 file_menu.py
独立
Does the process of "type checking" mean code is not executed?
是的,没错。类型检查器从不执行您的代码:相反,它 分析 它。类型检查器的实现方式与编译器的实现方式几乎相同,只是减去 "generate bytecode/assembly/machine code" 步骤。
这意味着您的类型检查器比 Python 解释器在 运行 时间内有更多的策略可用于解决导入循环(或任何类型的循环),因为它不需要尝试盲目导入模块。
例如,mypy 所做的基本上是逐个模块分析您的代码,跟踪正在定义的每个新 class/new 类型。在此过程中,如果 mypy 发现类型提示使用尚未定义的类型,请将其替换为占位符类型。
检查完所有模块后,检查是否仍有任何占位符类型浮动。如果是这样,请尝试使用我们目前收集的类型定义重新分析代码,并尽可能替换所有占位符。我们冲洗并重复,直到没有更多的占位符或我们迭代了太多次。
在那之后,mypy 假定所有剩余的占位符都是无效类型并报告错误。
相比之下,Python 解释器没有像这样重复重新分析模块的奢侈。它需要 运行 它看到的每个模块,反复重新 运行 模块可能会打破一些用户 code/user 的期望。
同样,Python 解释器没有能够交换我们分析模块的顺序的奢侈。相比之下,mypy 理论上可以以任意顺序分析您的模块,而忽略导入的内容——唯一的问题是它的效率非常低,因为我们需要大量迭代才能达到定点。
(因此,mypy 使用您的导入作为 建议 来决定以何种顺序分析模块。例如,如果模块 A 直接导入模块 B,我们可能要分析B 先。但是如果 A 在 if TYPE_CHECKING
之后导入 B,那么放宽顺序可能会帮助我们打破循环。)
我的包结构如下:
/prog
-- /ui
---- /menus
------ __init__.py
------ main_menu.py
------ file_menu.py
-- __init__.py
__init__.py
prog.py
这些是我的 import/classes 声明:
prog.py
:
from prog.ui.menus import MainMenu
/prog/ui/menus/__init__.py
:
from prog.ui.menus.file_menu import FileMenu
from prog.ui.menus.main_menu import MainMenu
main_menu.py
:
import tkinter as tk
from prog.ui.menus import FileMenu
class MainMenu(tk.Menu):
def __init__(self, master: tk.Tk, **kwargs):
super().__init__(master, **kwargs)
self.add_cascade(label='File', menu=FileMenu(self, tearoff=False))
[...]
file_menu.py
:
import tkinter as tk
from prog.ui.menus import MainMenu
class FileMenu(tk.Menu):
def __init__(self, master: MainMenu, **kwargs):
super().__init__(master, **kwargs)
self.add_command(label='Settings')
[...]
这会导致序列中的循环导入问题:
prog.py
-> __init__.py
-> main_menu.py
-> file_menu.py
-> main_menu.py
-> [...]
根据多次搜索,建议将导入更新为:
file_menu.py
import tkinter as tk
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from prog.ui.menus import MainMenu
class FileMenu(tk.Menu):
def __init__(self, master: 'MainMenu', **kwargs):
super().__init__(master, **kwargs)
self.add_command(label='Settings')
[...]
我已经阅读了 TYPE_CHECKING docs and the mypy docs 的用法,但我不了解如何使用此条件解决循环。是的,在运行时它可以工作,因为它的计算结果为 False
所以这是一个“操作分辨率”,但是它在类型检查期间如何不重新出现:
The TYPE_CHECKING constant defined by the typing module is False at runtime but True while type checking.
我对 mypy 了解不多,因此我看不出一旦条件评估为 True
问题就不会再次出现。 “运行时”和“类型检查”之间有什么不同? “类型检查”的过程是否意味着代码没有被执行?
备注:
这不是循环导入依赖问题,因此不需要依赖注入
这完全是静态分析的类型提示引起的循环
我知道以下导入选项(效果很好):
将
from [...] import [...]
替换为import [...]
在
MainMenu.__init__
进行进口,让file_menu.py
独立
Does the process of "type checking" mean code is not executed?
是的,没错。类型检查器从不执行您的代码:相反,它 分析 它。类型检查器的实现方式与编译器的实现方式几乎相同,只是减去 "generate bytecode/assembly/machine code" 步骤。
这意味着您的类型检查器比 Python 解释器在 运行 时间内有更多的策略可用于解决导入循环(或任何类型的循环),因为它不需要尝试盲目导入模块。
例如,mypy 所做的基本上是逐个模块分析您的代码,跟踪正在定义的每个新 class/new 类型。在此过程中,如果 mypy 发现类型提示使用尚未定义的类型,请将其替换为占位符类型。
检查完所有模块后,检查是否仍有任何占位符类型浮动。如果是这样,请尝试使用我们目前收集的类型定义重新分析代码,并尽可能替换所有占位符。我们冲洗并重复,直到没有更多的占位符或我们迭代了太多次。
在那之后,mypy 假定所有剩余的占位符都是无效类型并报告错误。
相比之下,Python 解释器没有像这样重复重新分析模块的奢侈。它需要 运行 它看到的每个模块,反复重新 运行 模块可能会打破一些用户 code/user 的期望。
同样,Python 解释器没有能够交换我们分析模块的顺序的奢侈。相比之下,mypy 理论上可以以任意顺序分析您的模块,而忽略导入的内容——唯一的问题是它的效率非常低,因为我们需要大量迭代才能达到定点。
(因此,mypy 使用您的导入作为 建议 来决定以何种顺序分析模块。例如,如果模块 A 直接导入模块 B,我们可能要分析B 先。但是如果 A 在 if TYPE_CHECKING
之后导入 B,那么放宽顺序可能会帮助我们打破循环。)