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

How do I properly import python modules in a multi directory project?

我有一个 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()

other.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'

执行此操作的更好方法是什么?

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

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

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

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

因此,为了解决此问题,我建议您使用 $ python -m 为 something.py & other.py 和 运行 编写单元测试(mod) 命令。 (我强烈推荐这种方法

但是......如果你真的让你现有的代码在没有太多mod化的情况下工作,那么你可以在something.py中添加这两行文件(这可行,但我不推荐这种方法

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

为了更好地理解,这里有一些参考资料:

PavanSkipo 已使用相对路径 解决了 ImportError 但文件只能通过 python -m utils.something 作为模块调用 - 而 python utils/something.py 将引发错误。 PavanSkipo 提到的使用 sys.path 的解决方法在我看来并不理想 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 好的,也许作为入口点 - 但在那种情况下,无论如何它应该作为一个包安装

我想你要找的就是这个。为了能够导入库并将其用作可执行文件,您可以这样做。首先,你应该制作 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!

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