简体   繁体   English

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

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

I have a python project with a basic setup that looks like this:我有一个 python 项目,其基本设置如下所示:

imptest.py

utils/something.py
utils/other.py

Here's what's in the scripts:这是脚本中的内容:

imptest.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东西.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其他.py

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

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

    do_other()

if __name__ == "__main__":
    main()

imptest.py is the main file that runs and calls the utils functions occasionally for some things. imptest.py 是偶尔为某些事情运行和调用 utils 函数的主文件。

And as you can see, I have commented out some lines in "something.py" where I am importing "other" module for testing.正如你所看到的,我已经在“something.py”中注释掉了一些行,我正在导入“other”模块进行测试。

But when I want to test certain functions in something.py, I have to run the file something.py and uncomment the import line.但是当我想测试something.py 中的某些函数时,我必须运行文件something.py 并取消注释导入行。

This feels like a bit of a clunky way of doing this.这感觉有点笨拙。

If I leave the如果我离开

import other

uncommented and run imptest.py, I get this error:取消注释并运行 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'

What's a better way of doing this?有什么更好的方法来做到这一点?

The problem here is the path, Consider this directory structure这里的问题是路径,考虑这个目录结构

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

When you try to import other using relative path in to something.py , then you would do something like from. import other当您尝试使用相对路径将other内容导入到something.py时,您会执行类似from. import other from. import other . from. import other This would work when you execute $ python something.py but would fail when you run $ python imptest.py because in the second scenario it searches for main/other.py which doesn't exist.这在您执行$ python something.py时会起作用,但在您运行$ python imptest.py时会失败,因为在第二种情况下,它会搜索不存在的 main/other.py。

So inorder to fix this issue, I would suggest that you write unit tests for something.py & other.py and run them using $ python -m (mod) command.因此,为了解决这个问题,我建议您为 something.py 和 other.py 编写单元测试并使用$ python -m (mod) 命令运行它们。 ( I highly recommend this approach ) 我强烈推荐这种方法

But.... if you really what your existing code to work without much modification then you can add these 2 lines in something.py file ( this works, but I don't recommend this approach )但是....如果您真的不需要太多修改就可以使用现有代码,那么您可以在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()

Here are some references to get better understanding:以下是一些参考资料,以便更好地理解:

PavanSkipo has solved the ImportError using relative path here but the files can then only be invoked as modules through python -m utils.something - whereas python utils/something.py will throw an error. PavanSkipo 已在此处使用相对路径解决了ImportError但文件只能通过python -m utils.something作为模块调用 - 而python utils/something.py将引发错误。 The workaround using sys.path as PavanSkipo has mentioned in not ideal in my opinion 1 .在我看来,使用sys.path作为 PavanSkipo 的解决方法并不理想1

OP's use of the statement if __name__=='__main__' suggest that the files are meant to be run directly in some ways - perhaps as scripts 2 . OP 对 if __name__=='__main__'语句的使用表明这些文件应该以某种方式直接运行 - 也许作为脚本2 In this case, we should use absolute imports and pip install -e.在这种情况下,我们应该使用绝对导入和pip install -e.

Using this approach allows the files to be run both as a module and as a script on its own.使用这种方法,文件既可以作为模块运行,也可以作为脚本单独运行。 In other words, all the following python utils/something.py , python -m utils.something and import utils.something will work just fine.换句话说,以下所有python utils/something.pypython -m utils.somethingimport utils.something都可以正常工作。


Add setup.py添加setup.py

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

Minimum content for setup.py setup.py的最小内容

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

After installing in development mode using pip install -e.使用pip install -e. you should use absolute import in utils/something.py like you do in imptest.py你应该在utils/something.py中使用绝对导入,就像在imptest.py中一样

import utils.other
...

1 It is best that the codes being tested should reflect the state of codes being used. 1被测试的代码最好反映正在使用的代码的 state。

2 Ok maybe as entry points - but in that case it should have been installed as a package anyway 2好吧,也许作为入口点 - 但在这种情况下,它应该已经安装为 package

I think what you're looking for is this.我想你要找的是这个。 In order to be able to import a library and use it as executable you can do this.为了能够导入库并将其用作可执行文件,您可以这样做。 First, you should make utils a package.首先,您应该将utils设为 package。 This means creation of __init__.py inside of it.这意味着在其中创建__init__.py

It's content should be this:它的内容应该是这样的:

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

This will make the import statement work as from utils import something, other in imptest.py and also import other in something.py .这将使 import 语句像from utils import something, other in imptest.pyimport other in something.py一样工作。

The final piece is as follows:最后一段如下:

→ 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!

This trick makes both import statements valid.这个技巧使两个导入语句都有效。 It basically adds the utils directory path to the search path, regardless where it is located.它基本上将utils目录路径添加到搜索路径中,而不管它位于何处。 As such, import other will work.因此, import other将起作用。 The __init__.py makes the utils directory a package ; __init__.py使utils目录成为package

You need to add an __init__.py file in your main directory so python knows it's a package.您需要在主目录中添加一个__init__.py文件,以便 python 知道它是 package。 Then you could add an import statement like so, from.other import function然后你可以像这样添加一个导入语句, from.other import function

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM