Python 3.9.5 中的相对导入

4

我的文件夹结构如下

./fff
├── __init__.py
├── fg
│   ├── __init__.py
│   └── settings
│       ├── __init__.py
│       └── settings.py
└── obng
    └── test.py

我想将fg/settings中的settings.py作为一个模块导入到test.py中。我已经添加了以下行:from ..fg.settings import settings。但是运行时,它给出以下错误:ImportError: attempted relative import with no known parent package。根据https://docs.python.org/3/reference/import.html#package-relative-imports,支持这种相对导入方式。我在这里做错了什么?

相对导入不能超过__main__包位置(或您启动Python进程的文件夹)。如果您运行test.py,则无法以相对方式从fg导入内容。 - GPhilo
如果你运行 python test.py,则不能使用 from ..。但是,如果在 fff 下有另一个 .py 文件,并且使用 import test,那么它将可以正常工作。 - Natthaphon Hongcharoen
1
唯一的绕过此问题的方法是添加路径变量,例如 sys.path.insert('/mnt/d/Repos/fff/fg') 然后 import fg.setting - Natthaphon Hongcharoen
4个回答

6

通常情况下,当您将Python模块作为主模块运行时(如python filename.py),您无法使用相对导入,但是可以使用__package__进行此操作。请记住,Python解析相对导入的方式是使用__package__

1- 在您的根目录- fff 中创建一个名为__init__.py的文件。(我看到您已经有了,这里只是为了完整性而提到)

2- 将以下代码放置在您的test.py模块的顶部:

if __name__ == '__main__' and not __package__:
    import sys
    sys.path.insert(0, <path to parent directory of root directory - fff>)
    __package__ = 'fff.obng'

注意:sys.path是Python搜索要导入的模块所在的位置。
3- 现在在上述代码之后放置您的相对导入语句(位于if语句内部,因为我们不想在导入test.py时出现混乱):
from ..fg.settings import settings

现在你可以调用你的test.py文件,它将能够无误地运行。我不建议使用这些hack方法,但在某些情况下,展示语言的灵活性并且能够做到自己想要的事情是有益的。
其他好的解决方案:绝对导入我认为比这个更容易和更干净。此外,可以查看@Mr_and_Mrs_D所提供的答案,另一个好的解决方案是使用-m命令行标志来运行你的模块。

2
请停止建议使用 sys.path 的 hack 方法。 - Mr_and_Mrs_D
1
@Mr_and_Mrs_D 您说得对,我添加了几句话来让人们避免使用黑客技巧。我的意图是展示Python动态调整功能的可能解决方案和能力。如果他想通过文件名运行脚本,像 python filename.py,这是唯一的解决方案。 - S.B
4
我是这个回答的忠实粉丝,请继续分享 sys.path 的技巧! - ayanami

6

这与您运行项目的方式有关 - 您应该从顶级包的父目录中运行,如下所示:

$ cd ../fff
$ python -m fff.obng.test # note no py

这样相对导入将被正确解析。直接从脚本文件夹运行脚本是一种反模式。


1
这解决了我所有的问题。在Python中执行代码时,好像有两个世界:模块世界和文件世界。当使用模块设置项目时,坚持使用模块方式运行事物,这似乎更容易/更一致。您是否有一个描述为什么直接从文件夹运行脚本是反模式的参考资料? - Paul P M
1
感谢@PaulPM - 例如请参见https://dev59.com/BGAg5IYBdhLWcg3wfbB6#23540051 - Mr_and_Mrs_D

1
相对导入是基于当前模块的名称。在运行时,Python将当前模块视为“顶级”模块,并使用其名称作为导入的起点。
python fff/obng/test.py

test.py的名称将为__main__,导入将无效。

有效的方法是在fff模块外部有另一个名为“test.py”的脚本,该脚本导入fff.obng.test。

fff_top
├── fff
│   ├── fg
│   │   ├── __init__.py
│   │   └── settings
│   │       ├── __init__.py
│   │       └── settings.py
│   ├── __init__.py
│   └── obng
│       ├── __init__.py
│       └── test.py
└── test.py

使用 fff_top/test.py:

import fff.obng.test

然后,运行名为“external”的test.py应该没问题:

python fft_top/test.py

或者,我建议完全放弃使用相对导入。一种方法是为您编写的每个包使用虚拟环境,例如使用venv库:

python -m venv venv

然后,在根目录中添加一个setup.py文件,内容如下:

from setuptools import setup, find_packages
setup(name="fff", packages=find_packages())

并且在obng/test.py中更改导入:

from fff.fg.settings import settings

最后,激活您的虚拟环境:

source venv/bin/activate

并以可编辑模式安装您的软件包:

pip install -e .

然后,在您完成上述所有步骤之后:
python fff/obng/test.py

应该可以工作。

感谢大家的回答。由于时间限制,我只是将设置文件settings.py的绝对路径(./...../fg/settings)添加到PYTHONPATH变量中,然后导入就像预期的那样工作了。我也会尝试这些实现方法。 - Sharath Prakash

0
在Linux中,您可以创建一个符号链接:
$ ln -s ../folder1 mymodules
$ python
>>> import mymodules.myfancymodule as fancy

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