问题:如何正确设置 MYPYPATH 以获取 mypy 的存根?

我一辈子都无法让 MyPy 找到不与其源代码位于同一位置的存根。这是我的项目的结构:

trymypy/
|- stubs/
|  \- foo.pyi
|- __init__.py
|- usefoo.py
\- foo.py
# foo.py
def foofunc(x):
    return str(x)
# usefoo.py
from trymypy.foo import foofunc

print(foofunc(5) + 5)
# stubs/foo.pyi
def foofunc(x: int) -> str: ...

我已将MYPYPATH环境变量设置为/full/path/to/trymypy/stubs,因此 MyPy 应该在stubs目录中查找我的.pyi文件。

这不应该通过类型检查。 MyPy 应该这样标记错误:

../trymypy/usefoo.py:3: error: Unsupported operand types for + ("str" and "int")

相反,MyPy 不会标记任何错误,因为它没有读取存根文件。如果我将存根文件foo.pyi移动到目录的根项目,与foo.py位于同一位置,它会正确标记,这表明MYPYPATH没有被拾取,或者没有被正确定义。

我还尝试在配置文件mypy.ini中设置mypy_path:

[mypy]
python_version = 3.7
mypy_path = /full/path/to/trymypy/stubs

mypy.ini中的其他配置选项确实被选中(例如python_version),因此 MyPy 正在查看该文件并读取它。

完全被这里难住了。这是一个(非常)简单的示例,看起来它应该按照 MyPy 的文档工作。我已经用完了变量来进行实验以使其正常工作。

我正在使用 Python 3.7 并在仅安装了 mypy 的虚拟环境中工作。

解答

长话短说:您可能希望将foo.pyi文件与foo.py一起移动到顶级trymypy文件夹中,而不是拥有一个单独的“存根”目录。


长话短说,您的设置目前存在两个问题。

第一个问题是存根的文件夹结构需要反映底层代码的结构方式。所以既然你想做from trymypy.foo import blah,你需要调整你的文件夹结构看起来像这样:

trymypy/
|- stubs/
|  |- trymypy/
|  |  |- __init__.pyi
|  \  \- foo.pyi
|- __init__.py
|- usefoo.py
\- foo.py

您应该继续将 mypypath 设置为指向trymypy/stubs。您可以使用绝对路径或相对路径。

更大的第二个问题是您的存根最终可能会被隐藏和忽略,具体取决于您调用 mypy 的准确程度。例如,如果您从trymypy文件夹外部运行mypy -p trymypy,mypy 将首先解析每个trymypy及其直接包含的两个子模块(trymypy.__init__trymypy.usefootrymypy.foo)。

并且一旦trymypy.foo被加载,mypy 就不会再尝试重新加载它,这意味着它永远不会检查你指定的存根。

但是如果你尝试对单个文件进行类型检查(例如mypy -m trymypy.usefoomypy -p trymypy.usefoo),mypy 不会尝试加载trymypy中的所有内容,这意味着它可以使用典型的导入解析规则找到存根。

您可以通过传入-v标志自己确认所有这些行为,该标志以详细模式运行 mypy 并准确打印出加载的内容。每次运行前一定要删除.mypy_cache目录。

注意:老实说,我真的不知道这种行为差异是故意的还是 mypy 中的错误。导入规则非常微妙。


幸运的是,修复很简单:只需将您的foo.pyi文件移动到顶级trymypy文件夹中,如下所示:

trymypy/
|- __init__.py
|- usefoo.py
|- foo.py
\- foo.pyi

现在,无论按什么顺序导入,mypy 总是会同时找到foo.pyfoo.pyi,因为这两个文件位于同一个目录中。并且每当pypyi文件位于同一目录中时,pyi文件总是胜出(并且 py 文件被忽略)。


关于这个新的文件夹结构,您可能有两个后续问题:

  1. 有没有办法使用foo.pyi中的类型提示对foo.py的内容进行类型检查?

答案是否定的,目前没有办法。如果存在foo.pyi,则 mypy 基本上完全忽略了foo.py的主体。有人对添加对此功能的支持感兴趣,因此您也许可以订阅链接的 Github 问题以获取更新。

  1. 创建一个单独的“存根”文件夹最终在这里没有用。那么它什么时候有用呢?

答案是,当您想为第三方库添加存根时,它主要有用。实际上,我对“为您自己的代码添加存根”工作流程没有太多经验,但我的理解是,这些存根通常以上述方式“内联”。

Logo

Python社区为您提供最前沿的新闻资讯和知识内容

更多推荐