如何在多目录项目中正确导入 python 模块?

Posted

技术标签:

【中文标题】如何在多目录项目中正确导入 python 模块?【英文标题】:How do I properly import python modules in a multi directory project? 【发布时间】:2021-03-13 23:49:35 【问题描述】:

我有一个基本设置如下的 python 项目:

imptest.py

utils/something.py
utils/other.py

以下是脚本中的内容:

imptest.py

​​>
#!./venv/bin/python

import utils.something as something
import utils.other as other

def main():
    """
    Main function.
    """

    something.do_something()
    other.do_other()

if __name__ == "__main__":
    main()

something.py

​​>
#import other

def do_something():
    print("I am doing something")


def main():
    """
    Main function
    """

    do_something()
    #other.do_other()

if __name__ == "__main__":
    main()

其他.py

​​>
def do_other():
    print("do other thing!")

def main():
    """
    Main function
    """

    do_other()

if __name__ == "__main__":
    main()

imptest.py 是主文件,它偶尔会运行并调用 utils 函数。

如您所见,我在“something.py”中注释掉了一些行,我正在导入“其他”模块进行测试。

但是当我想测试something.py中的某些功能时,我必须运行文件something.py并取消对导入行的注释。

这感觉有点笨拙。

如果我离开了

import other

取消注释并运行 imptest.py,我得到这个错误:

Traceback (most recent call last):
  File "imptest.py", line 5, in <module>
    import utils.something as something
  File "...../projects/imptest/utils/something.py", line 3, in <module>
    import other
ModuleNotFoundError: No module named 'other'

有什么更好的方法?

【问题讨论】:

测试你的 sn-ps。这些扩展会搞砸事情 您介意发布minimal, reproducible example 而不是描述假设案例吗? “一切正常。”:在这种情况下,粘贴您的实际代码 嘿,我添加了更多上下文/代码。请看看这是否更有用。 【参考方案1】:

这里的问题是路径,考虑这个目录结构

main
 - utils/something.py
 - utils/other.py
 imptest.py

当您尝试使用相对路径将other 导入到something.py 时,您会执行类似from . import other 的操作。这在您执行 $ python something.py 时会起作用,但在您运行 $ python imptest.py 时会失败,因为在第二种情况下,它会搜索不存在的 main/other.py。

所以为了解决这个问题,我建议你为 something.py 和 other.py 编写单元测试并使用$ python -m (mod) 命令运行它们。 (我强烈推荐这种方法

但是....不推荐这种方法)

import sys, os
sys.path.append(os.getcwd()) # Adding path to this module folder into sys path
import utils.other as other

def do_something():
    print("I am doing something")


def main():
    """
    Main function
    """

    do_something()
    other.do_other()

if __name__ == "__main__":
    main()

这里有一些参考资料可以帮助你更好地理解:

Unit testing in python Absolute vs Relative Imports in python

【讨论】:

是的,我在研究这个时得出了一些类似的结论。我可以运行实用程序。使用 python -m utils.something 文件,它不会引发错误。我现在必须弄清楚测试。我只是想快速测试 utils 脚本函数,而不使用“断言”并在使用函数之前尝试确定函数的输出你知道我的意思吗?感谢您的帮助。【参考方案2】:

PavanSkipo 已使用相对路径 here 解决了 ImportError 问题,但这些文件只能通过 python -m utils.something 作为模块调用 - 而 python utils/something.py 将引发错误。在我看来,使用sys.path 作为 PavanSkipo 的解决方法并不理想1

OP 对 if __name__=='__main__' 语句的使用表明文件旨在以某些方式直接运行 - 可能作为脚本 2。在这种情况下,我们应该使用绝对导入和pip install -e .

使用这种方法,文件既可以作为模块运行,也可以作为脚本单独运行。换句话说,以下所有python utils/something.pypython -m utils.somethingimport utils.something 都可以正常工作。


添加setup.py

setup.py
imptest.py
utils/something.py
utils/other.py

setup.py 的最少内容

from setuptools import setup, find_packages
setup(name='utils', version='0.1', packages=find_packages())

使用pip install -e . 在开发模式下安装后,您应该像在imptest.py 中一样在utils/something.py 中使用绝对导入

import utils.other
...

1 被测试的代码最好能反映正在使用的代码的状态。

2 好吧,也许可以作为入口点 - 但在这种情况下,它应该已经作为一个包安装了

【讨论】:

【参考方案3】:

我想你要找的是这个。为了能够导入库并将其用作可执行文件,您可以这样做。首先,您应该将utils 制作成一个包。这意味着在其中创建__init__.py

内容应该是这样的:

→ cat utils/__init__.py
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))

这将使导入语句在imptest.py 中作为from utils import something, other 工作,在something.py 中也作为import other 工作。

最后一段如下:

→ tree
.
 |-imptest.py
 |-utils
 | |-__init__.py
 | |-other.py
 | |-something.py


→ cat imptest.py
#!./venv/bin/python
from utils import something, other

def main():
    """
    Main function.
    """

    something.do_something()
    other.do_other()

if __name__ == "__main__":
    main()

→ cat utils/something.py
import other

def do_something():
    print("I am doing something")


def main():
    """
    Main function
    """

    do_something()
    other.do_other()

if __name__ == "__main__":
    main()

→ python3 imptest.py
I am doing something
do other thing!

→ python3 utils/something.py
I am doing something
do other thing!

这个技巧使两个 import 语句都有效。它基本上将utils 目录路径添加到搜索路径中,而不管它位于何处。因此,import other 将起作用。 __init__.py 使utils 目录成为package;

【讨论】:

不,运行 imptest.py 失败,在 something.py 中导入 other 导致模块 other 在 something.py 中找不到。【参考方案4】:

您需要在主目录中添加一个__init__.py 文件,以便python 知道它是一个包。然后你可以像这样从 .other 导入函数中添加一个导入语句

【讨论】:

我修改了帖子。我不认为这正是我所追求的解决方案。 如果我不想导入函数而是导入整个模块怎么办?我想这不是一个好主意? 这不能解决 - 与 ModuleNotFound 错误无关

以上是关于如何在多目录项目中正确导入 python 模块?的主要内容,如果未能解决你的问题,请参考以下文章

如何在python3中正确导入同一目录下的模块

Python:从项目层次结构中同一级别的另一个目录导入模块

python的Django views 文件中引入模块问题

在多模块 SDK Android 库项目中配置 proguard

如何在 Python 模块中正确使用相对或绝对导入?

对如何从模块中的其他目录/父目录导入感到困惑(python 3)