无法使用__init__.py别名使Mypy工作

5
以下是我的问题的最简示例:
[test/__init__.py]
from test.test1 import Test1
from test.test2 import Test2

[test/test1.py]
class Test1:
    pass

[test/test2.py]
from test import Test1

class Test2:
    pass

模块或 __init__.py 的 Mypy 输出:

test/test2.py:1: error: Module 'test' has no attribute 'Test1'

这段代码不仅适用于Python 2,也适用于Python 3。

1个回答

4
这是因为你的代码存在一个“导入循环” -- 为了让mypy解析test/__init__.py文件,它需要准确地理解Test2是什么。(毕竟,如果你决定在该文件中稍后使用Test2或调用其方法呢?那么mypy就需要知道输出结果)。所以,它遇到了这个导入,有效地暂停,并跳转到尝试理解test/test2.py文件。但是,在test/test2.py文件内部,我们遇到了完全相同的问题--我们看到了导入,需要跳回test/__init__.py文件才能理解Test1是什么...但我们还没有完成解析该文件!
这就是mypy与Python运行时的不同之处-- mypy只能一次解析整个文件,而Python运行时实际上会暂停执行以运行test/test2.py。这意味着当你执行from test import Test1时,test模块具有部分完成的符号空间,该空间目前仅包含Test1,而不是同时包含Test1Test2,这就是为什么你的代码在运行时可以正常工作。
在这种情况下,解决方法是修改test/test2.py中的导入方式为:
from test.test1 import Test1

这会打破导入循环。


*实际上,mypy并不是这样做的--它实际上是通过首先识别所有强连通分量(SCC)来尝试解决导入循环的问题--每个SCC基本上都是一个导入循环。

然后,它应用一些启发式算法来确定SCC内应该以什么顺序处理文件,但这是一个不完美的过程,无法解决所有导入循环。

例如,在您的情况下,无论我们首先处理test还是test.test2,都会遇到问题。

您可以通过使用-v标志(用于详细模式)重新运行mypy来查看mypy识别的SCC。

您可以在此处查看mypy使用的算法的更多细节。有关导入循环解决算法的具体详细信息可以在稍低位置这里找到。

关于mypy用于排序SCC的确切启发式的信息可以在这里找到。(基本上,我们用于导入某些内容的确切语法可以提供有关如何排序该SCC的一些提示)。


仍然不清楚。我有一个名为test.test1的SCC和一个名为testtest.test2的SCC。Mypy将首先处理第一个,然后它将了解Test1并能够在处理__init__.pytest2.py文件时使用Test1类类型提示。根据-v输出,这正是发生的事情。我希望对于同一SCC中另一个文件使用的类型提示会有一些问题,但不是这个原因。为什么? - freopen
啊,我明白了,它是test模块的一部分,名称为test.Test1,而不是test1模块的一部分。 - freopen
1
@freopen -- 是的,完全正确。除非处理test,否则mypy不会知道test.Test1test.test1.Test1有任何关系。如果我没记错的话,这种关联是在后面的流程中建立的。 - Michael0x2a

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接