无法从另一个目录导入Python模块

3

我无法从另一个目录中的Python文件进行导入。目录结构:

some_root/
  - __init__.py
  - dir_0/
    - __init__.py
    - dir_1/
      - __init__.py
      - file_1.py
    - dir_2/
      - __init__.py
      - file_2.py

file_1.py 中包含一些导出成员:

# file_1.py
def foo():
    pass

file_2.py试图从file_1.py导入成员:

# file_2.py
from dir_0.dir_1.file_1 import foo

但是使用绝对或相对导入似乎都不起作用。如何正确地进行Python的导入?如果能避免使用sys.path.insert那就太好了,但如果没有办法,我想那就是现实情况。


1
看一下 pip--editable 选项。你必须在 开发模式 下 "安装" 你的包。这将让你了解如何重新组织你的项目和包文件夹的布局:https://dev59.com/I3cPtIcB2Jgan1znOsTg#73106602 但你也必须考虑这样的导入结构是否有意义。 - buhtz
1
你如何执行 file_2.py?“简单”的答案是,dir_0 不在 Python 搜索路径上的任何目录中。请记住,在启动时,当前工作目录不会被添加到搜索路径中,只有包含脚本的目录会被添加。 - chepner
1
通常情况下,您不会将脚本存储在这样的包中。请将您的脚本保存在“some_root”中,并最多使用它们来调用导入模块中的入口点。 - chepner
“真正”安装的脚本会被放置在/usr/bin这样的位置,而任何包都会被安装在/usr/lib/python/site-packages这样的位置。只有在尝试从源代码执行而没有进行安装时,才会出现此类导入问题。 - chepner
1
在考虑导入系统的工作原理时,清晰地区分文件、模块(以及目录和包)是有帮助的。您不会导入文件和目录;您导入包和模块,当必要时,导入系统将模块名称转换为文件路径。 - chepner
2个回答

3

不要随意修改搜索路径

你很明智,不要随意操作 sys.path。这不是推荐的做法,总是一个丑陋的权宜之计。有更好的解决方案。

重新组织你的文件夹结构

参见 Python官方文档中关于打包的部分

区分项目文件夹包文件夹。我们假设你的some_root文件夹是包文件夹,并且也是包的名称(在import语句中使用)。建议将包文件夹放入名为src的文件夹中。在它上面是项目文件夹some_project。这种项目文件夹结构也被称为“Src-Layout”。

在你的情况下,应该像这样:

some_project
└── src
    └── some_root
        ├── dir_0
        │   ├── dir_1
        │   │   ├── file_1.py
        │   │   └── __init__.py
        │   ├── dir_2
        │   │   ├── file_2.py
        │   │   └── __init__.py
        │   └── __init__.py
        └── __init__.py

使您的软件包可安装

创建一个名为some_project/setup.cfg 的文件,并将以下内容添加到其中。保留第5和6行的换行和缩进,它们必须是这样的,但我不知道为什么。

[metadata]
name = some_project

[options]
package_dir=
    =src
packages = find:
zip_safe = False
python_requires = >= 3
[options.packages.find]
where = src
exclude =
    tests*
    .gitignore

创建some_project/setup.py文件,其内容如下:
from setuptools import setup
setup()

安装

这不是一个常规的安装过程。请查看开发模式以理解其实际含义。该软件包并未复制到/usr/lib/python/site-packages目录中,只有链接被创建。

进入项目文件夹some_project并运行:

python3 -m pip install --editable .

在结尾不要忘记加上.。根据你的操作系统和环境,可能需要将python3替换为py -3python或其他内容。

导入

您的file_2.py

import some_root
import some_root.dir_0
import some_root.dir_0.dir_1
from some_root.dir_0.dir_1 import file_1

file_1.foo()

但正如其他人在评论中所说的那样,改进您的文件和文件夹结构,减少其复杂性。


2
默认情况下,Python搜索路径包括在安装Python时实际上已经确定的目录,例如:/usr/lib/python/site-packages。
当您运行脚本时,包含正在执行的脚本的目录会被添加到路径中。尤其重要的是,当前工作目录不会被添加到路径中,出于安全考虑。(您不希望脚本根据其调用环境而以不同的方式工作,可能导入您未预期的模块。这也是强烈建议将"."从PATH变量中删除的原因)。
当您执行file_2.py时,some_root/dir_0/dir_2会被添加到路径中,但如果some_root是当前工作目录,则some_root本身不在搜索路径中。这就是为什么import dir_0.dir_1.file_1失败的原因。相对导入也会失败,因为some_root不是用于解析相对导入的根(或任何)包。
通常,如果您想在不安装代码的情况下运行代码,您将把脚本放在some_root目录中。
some_root/
  file_a.py
 
  - __init__.py
  - dir_0/
    - __init__.py
    - dir_1/
      - __init__.py
      - file_1.py
    - dir_2/
      - __init__.py
        file_2.py

file_a.py可以非常简单,例如:

from dir_0.dir_2.file_2 import main


if __name__ == '__main__':
    main()

file_2提供了一个入口点,而不是直接被执行。它仍然可以作为可执行文件,只是像导入file_2.py的任何其他脚本一样使用main而不是直接跳转到其代码。


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