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不理会
“类型检查”的过程是否意味着不执行代码?
对,就是这样。类型检查器永远不会执行您的代码:相反,它会分析它。类型检查器的实现方式与编译器的实现方式几乎相同,只是去掉了“生成字节码/汇编/机器代码”步骤。
这意味着您的类型检查器比 Python 解释器在运行时拥有更多可用于解析导入循环(或任何类型的循环)的策略,因为它不需要盲目地尝试导入模块。
例如,mypy 所做的基本上是从逐个模块分析您的代码开始,跟踪正在定义的每个新类/新类型。在此过程中,如果 mypy 看到使用尚未定义的类型的类型提示,请将其替换为占位符类型。
一旦我们完成了所有模块的检查,检查并查看是否还有任何占位符类型在浮动。如果是这样,请尝试使用我们迄今为止收集的类型定义重新分析代码,并尽可能替换所有占位符。我们冲洗并重复,直到没有更多的占位符或者我们迭代了太多次。
在那之后,mypy 假定任何剩余的占位符都是无效类型并报告错误。
相比之下,Python 解释器没有像这样反复重新分析模块的奢侈。它需要运行它看到的每个模块,并且反复重新运行模块可能会破坏一些用户代码/用户期望。
类似地,Python 解释器也不能随意交换我们分析模块的顺序。相比之下,mypy 理论上可以以任意顺序分析您的模块,而忽略导入的内容——唯一的问题是它的效率非常低,因为我们需要大量迭代才能达到固定点。
(所以相反,mypy 使用您的导入作为建议来决定分析模块的顺序。例如,如果模块 A 直接导入模块 B,我们可能想先分析 B。但是如果 A 在后面导入 B if TYPE_CHECKING,那么放松一下可能就好了订购如果它会帮助我们打破一个循环。)