在Python 3中,如何从同一包内和包外导入模块?

25

好的,情景非常简单。我有这个文件结构:

.
├── interface.py
├── pkg
│   ├── __init__.py
│   ├── mod1.py
│   ├── mod2.py

这是我的条件:

  • mod2需要导入mod1。
  • interface.py和mod2都需要独立运行为主要脚本。 如果您愿意,可以将interface视为实际程序,将mod2视为包的内部测试器。

因此,在Python 2中,我只需在mod2.py内部执行import mod1,然后python2 mod2.pypython2 interface.py将按预期工作。

然而,这就是我不太理解的部分,使用Python 3.5.2,如果我执行import mod1; 然后我可以执行python3 mod2.py,但python3 interface.py抛出:ImportError: No module named 'mod1' :(

因此,显然,Python 3建议使用import pkg.mod1来避免与内置模块冲突。 好的,如果我这样做,我就可以执行python3 interface.py; 但是然后我不能python3 mod2.py,因为:ImportError: No module named 'pkg'

同样,如果我使用相对导入: from . import mod1然后python3 interface.py就可以工作; 但是mod2.py会说SystemError: Parent module '' not loaded, cannot perform relative import :( :(

我找到的唯一“解决方案”是向上移动一个文件夹,然后执行python -m pkg.mod2,然后它就可以工作了。 但是我们必须将包前缀pkg添加到该包中的其他模块的每个导入中吗? 更重要的是,要运行包内部的任何脚本,我是否必须记住向上移动一个文件夹并使用-m开关? 那是唯一的方法吗??

我很困惑。 这种情况在Python 2中非常简单明了,但在Python 3中看起来很笨拙。

更新:在此处上传了那些带有(上述“解决方案”)工作源代码的文件:https://gitlab.com/Akronix/test_python3_packages。 请注意,我仍然不喜欢它,而且比Python2解决方案难看得多。


我已经阅读过的相关SO问题:

相关链接:


这是我目前找到的最好(尽管令人失望)的答案:https://dev59.com/y3VD5IYBdhLWcg3wJIAK#8195271 - Akronix
3
在您的mod2.py中,您可以使用相对导入来代替from pkg.mod1 import fun,例如from .mod1 import funfrom .import mod1,这将避免提及包的名称。这两种方式都适用于Python 2和3。此处有一份好的阅读材料,关于相对导入的文章 - init_js
2个回答

16

简述:

  • 使用python -m pkg.mod2运行代码。
  • 使用from . import mod1导入代码。

我唯一找到的“解决方案”是返回上一级目录并运行python -m pkg.mod2,这样就可以正常工作了。

使用-m开关确实是“唯一”的解决方案-它在之前已经是唯一的解决方案了。旧的行为只是凭着运气能够工作;甚至在不修改代码的情况下也可能被破坏。

返回“上一级目录”只是将您的软件包添加到搜索路径中。安装您的软件包或修改搜索路径同样有效。有关详细信息,请参见下文。

但是我们必须在每个要导入该包中其他模块的地方都加上包前缀pkg吗?

你必须拥有对你的软件包的引用 - 否则很难确定你想要哪个模块。软件包引用可以是绝对的或相对的。

相对导入通常是您想要的。它省去了显式编写pkg,使重构和移动模块更容易。

# inside mod1.py
# import mod2 - this is wrong! It can pull in an arbitrary mod2 module
# these are correct, they uniquely identify the module
import pkg.mod2
from pkg import mod2
from . import mod2
from .mod2 import foo  # if pkg.mod2.foo exists
请注意,您始终可以使用<import> as <name>将导入绑定到其他名称。例如import pkg.mod2 as mod2让您仅使用模块名称。
如果您的包已经正确安装,您可以从任何位置使用-m开关来运行其中的任何脚本。例如,您始终可以使用python3 -m json.tool
echo '{"json":"obj"}' | python -m json.tool
如果您的软件包尚未安装,您可以设置 PYTHONPATH 到其基本目录。这会将您的软件包包含在搜索路径中,并允许 -m 开关正确地找到它。
如果您在可执行文件所在的目录中,可以执行 export PYTHONPATH="$(pwd)/.." 快速挂载软件包以供导入。

我很困惑。这种情况在 Python 2 中非常简单,但在 Python 3 中看起来很奇怪。

在 Python 2 中,这种情况基本上已经失效了。虽然在许多情况下很简单,但在其他任何情况下都难以修复或根本无法修复。
新行为在简单情况下更加奇怪,但在任何情况下都更加强大和可靠。

我希望您能指导我有关以下链接中的第3种情况。作者在第3种情况中说解决方案一(使用-m)不可能。您能告诉我这是否属实吗?https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html#miscellaneous-topics-and-readings-not-covered-here-but-worth-exploring - variable
@variable 抱歉,很难理解你的意思。从快速扫描来看,我会说这不是真的。我建议你在Stackoverflow.com提出一个新问题或访问Python聊天室。 - MisterMiyagi

-2

我有类似的问题。我通过在包文件夹中的__init__.py文件中添加以下内容来解决它:

import sys 
sys.path.insert(0,".package_name")

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