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

6

我有一个 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”文件中我已经注释掉了一些代码,其中我导入了“other”模块进行测试。

但是当我要测试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'

有更好的方法吗?


测试你的代码片段。那些扩展会搞砸一切。 - Mad Physicist
2
你介意提供一个最小化可重现的例子而不是描述一个假设情况吗? - Selcuk
一切都正常运作良好。 - Mad Physicist
嘿,我添加了更多的上下文/代码。请看看这是否更有用。 - ScipioAfricanus
4个回答

6
这里的问题在于路径,考虑以下目录结构。
main
 - utils/something.py
 - utils/other.py
 imptest.py

当你试图通过相对路径在something.py中导入other时,你需要像这样做from . import other。当你执行$ python something.py时,这将起作用,但当你运行$ python imptest.py时会失败,因为在第二种情况下它会搜索不存在的main/other.py
因此,为了解决这个问题,我建议你为something.pyother.py编写单元测试,并使用$ python -m(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()

以下是一些参考资料,可帮助您更好地理解:


是的,我研究过后得出了一些类似的结论。我可以使用 python -m utils.something 运行 utils 文件,而且不会报错。现在我得搞清楚如何进行测试。我只想快速测试 utils 脚本函数,而不使用 "assert" 并在使用函数之前尝试确定函数的输出,你知道我的意思吗?谢谢你的帮助。 - ScipioAfricanus

0
PavanSkipo使用相对路径here解决了ImportError的问题,但是文件只能通过python -m utils.something作为模块调用,而python utils/something.py会抛出错误。在我看来,使用PavanSkipo提到的sys.path的解决方法并不理想1
OP使用语句__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 . 在开发模式下安装后,您应该在 utils/something.py 中使用绝对导入,就像在 imptest.py 中一样。

import utils.other
...

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

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


0

我认为你正在寻找的是这个。为了能够导入一个库并将其用作可执行文件,您可以这样做。首先,您应该将 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目录变成了一个


不行,运行imptest.py失败了,因为在something.py中导入other模块时找不到它。 - ScipioAfricanus

-2
你需要在主目录下添加一个名为 __init__.py 的文件,这样 Python 才知道它是一个包。然后你可以像下面这样添加一个导入语句:from .other import function

我修改了这篇文章。我认为这并不是我想要的确切解决方案。 - ScipioAfricanus
如果我不想导入一个函数,而是整个模块呢?我猜那不是一个好主意? - ScipioAfricanus
1
这不能解决 - 与ModuleNotFound错误无关 - Leonardus Chen

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