mypy如何使用type.TYPE_CHECKING解决循环导入批注问题?

时间:2020-05-01 15:33:07

标签: python import annotations type-hinting mypy

我的包裹结构如下:

/prog
-- /ui
---- /menus
------ __init__.py
------ main_menu.py
------ file_menu.py
-- __init__.py
__init__.py
prog.py

这些是我的import / classs语句:

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')

    [...]

我已经阅读了Python文档和mypy文档的用法,但是我不了解如何使用此条件解决周期。是的,它在运行时有效,因为它的评估结果为False,因此这是一个“操作解决方案”,但是如何在类型检查期间不重新出现呢?

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

我对mypy不太了解,因此我无法看到条件一旦评估为True后该问题将不会再次出现。 “运行时”和“类型检查”之间有什么不同? “类型检查”过程是否意味着代码未执行?

注意事项(无需阅读,但如果需要,可以提供更多详细信息):

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

  • 严格来说,这是由类型提示引起的静态分析周期

  • 我知道以下导入选项(可以正常使用):

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

    • MainMenu.__init__中进行进口,而file_menu.py则一个人

1 个答案:

答案 0 :(得分:1)

“类型检查”过程是否不执行代码?

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

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

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

完成所有模块的检查之后,请检查并查看是否还有任何占位符类型在浮动。如果是这样,请尝试使用我们到目前为止收集的类型定义重新分析代码,并在可能的情况下替换任何占位符。我们冲洗并重复进行,直到不再有占位符或迭代次数过多为止。

此后,mypy假定所有剩余的占位符只是无效的类型,并报告错误。


相比之下,Python解释器并不具有能够反复重新分析像这样的模块的优势。它需要运行它看到的每个模块,并且反复重新运行这些模块可能会破坏一些用户代码/用户期望。

类似地,Python解释器并不具有能够仅围绕我们分析模块的顺序进行交换的奢望。相比之下,mypy可以在理论上以任意顺序分析模块,而无需考虑导入了什么内容-唯一的不足是,由于我们需要大量迭代才能达到固定点,因此它的效率极低。

(因此,mypy使用您的导入作为建议来决定分析模块的顺序。例如,如果模块A直接导入模块B,我们可能首先要分析B。但是,如果在if TYPE_CHECKING之后输入B,如果可以帮助我们打破一个周期,可以放宽顺序。)