mypy是如何使用打字的.TYPE_CHECKING来解决循环导入注释问题



我有以下包的结构:

/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文档和mypy文档,但我不知道使用这个条件是如何解决循环的。是的,在运行时它工作,因为它的求值为False,所以这是一个"0";操作分辨率";,但是,在类型检查过程中,它是如何不重新出现的:

类型模块定义的TYPE_CHECKING常量在运行时为False,但在类型检查时为True。

我对mypy了解不多,因此我看不出一旦条件求值为True,问题将不会再次出现。在";运行时";以及";类型检查";?";类型检查";意思是代码没有执行?

注意:

  • 这不是一个循环导入依赖性问题,因此不需要依赖性注入

  • 对于静态分析来说,这是一个由类型暗示引起的循环

  • 我知道以下导入选项(工作正常(:

    • import [...]替换from [...] import [...]

    • MainMenu.__init__中进行导入,不使用file_menu.py

"类型检查"过程是否意味着代码不执行?

没错。类型检查器从不执行您的代码:相反,它会分析它。类型检查器的实现方式与编译器的实现方式几乎相同,只是减去了"生成字节码/汇编/机器代码"步骤。

这意味着,与Python解释器在运行时相比,类型检查器有更多的策略可用于解析导入周期(或任何类型的周期(,因为它不需要尝试盲目导入模块。

例如,mypy所做的基本上是从逐个模块分析代码开始,跟踪正在定义的每个新类/新类型。在此过程中,如果mypy使用尚未定义的类型看到类型提示,请用占位符类型替换它。

一旦我们完成了对所有模块的检查,请检查是否还有任何占位符类型浮动。如果是这样,请尝试使用我们迄今为止收集的类型定义重新分析代码,尽可能替换任何占位符。我们冲洗并重复,直到没有更多的占位符,或者我们重复了太多次。

在那之后,mypy假设所有剩余的占位符都是无效类型,并报告一个错误。


相比之下,Python解释器没有能够重复重新分析这样的模块的奢侈。它需要运行它看到的每个模块,重复重新运行模块可能会打破一些用户代码/用户的期望。

类似地,Python解释器也不能只交换我们分析模块的顺序。相比之下,mypy理论上可以以任意顺序分析模块,忽略什么导入什么——唯一的问题是,它的效率非常低,因为我们需要大量迭代才能达到固定点。

(因此,mypy使用您的导入作为建议来决定分析模块的顺序。例如,如果模块A直接导入模块B,我们可能想先分析B。但如果A在if TYPE_CHECKING之后导入B,如果这有助于我们打破循环,则放宽排序可能是好的。(

相关内容

最新更新