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

pst*_*tix 5 python import annotations type-hinting mypy

我有一个包的以下结构:

/prog
-- /ui
---- /menus
------ __init__.py
------ main_menu.py
------ file_menu.py
-- __init__.py
__init__.py
prog.py
Run Code Online (Sandbox Code Playgroud)

这些是我的导入/类语句:

prog.py

from prog.ui.menus import MainMenu
Run Code Online (Sandbox Code Playgroud)

/prog/ui/menus/__init__.py

from prog.ui.menus.file_menu import FileMenu
from prog.ui.menus.main_menu import MainMenu
Run Code Online (Sandbox Code Playgroud)

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

    [...]
Run Code Online (Sandbox Code Playgroud)

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

    [...]
Run Code Online (Sandbox Code Playgroud)

这将导致序列中的循环导入问题:

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

    [...]
Run Code Online (Sandbox Code Playgroud)

我已经阅读了有关用法的TYPE_CHECKING 文档mypy 文档,但我没有遵循使用此条件如何解决循环的问题。是的,在运行时它可以工作,因为它评估为False“操作解决方案”,但是在类型检查期间它如何不重新出现:

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

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

笔记:

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

  • 这严格来说是由静态分析的类型提示引起的循环

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

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

    • 进行进口MainMenu.__init__file_menu.py不理会

Mic*_*x2a 8

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

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

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

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

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

在那之后,mypy 假定任何剩余的占位符都是无效类型并报告错误。


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

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

(所以相反,mypy 使用您的导入作为建议来决定分析模块的顺序。例如,如果模块 A 直接导入模块 B,我们可能想先分析 B。但是如果 A 在后面导入 B if TYPE_CHECKING,那么放松一下可能就好了订购如果它会帮助我们打破一个循环。)