我有以下包的结构:
/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,如果这有助于我们打破循环,则放宽排序可能是好的。(