我首先要说的是,过去几个月我一直在重复这个问题。无论我研究多少,我似乎都找不到满意的答案。我希望这里的社区可以帮助我。
基本问题 - 考虑python包和python模块的这种结构。
|- src
|- pkg_1
|- foo_1.py
|- foo_2.py
|- __init__.py
|- pkg2
|- bar_1.py
|- bar_2.py
|- __init__.py
|- do_stuff.py
|- __init__.py
假设模块bar_2
需要从模块foo_1
导入。我的选择是无数的,但有些人很快出现。
(我的首选方式)模块bar_2
可以from ..pkg_1 import foo_1
。这很好,因为它不需要对模块中的路径进行硬编码,因此允许灵活性,易于维护。在do_stuff.py
如果我然后写from src.pkg_2 import bar_2
然后运行,我就是金色的。以下是一个示例设置:
foo_1.py:
class Foo_1():
def __init__(self):
print('Hello from foo_1!')
bar_2.py:
from ..pkg_1 import foo_1
class Bar_2():
def __init__(self):
print('Hello from bar_2!')
foo_1.Foo_1() #Prints foo_1 message!
do_stuff.py:
from src.pkg_2 import bar_2
bar_2.Bar_2()
控制台打印:
Hello from bar_2!
Hello from foo_1!
一切都很好。但是,请考虑下一个场景。
现在假设我想将bar_2
作为__main__
运行,如下所示:
from ..pkg_1 import foo_1
class Bar_2():
def __init__(self):
print('Hello from bar_2!')
foo_1.Foo_1()
if __name__ == '__main__':
Bar_2()
引发SystemError
:from ..pkg_1 import foo_1
SystemError: Parent module '' not loaded, cannot perform relative import
比我承认的要长得多,我不明白这个的原因。但是,解决方案在于,当您直接运行模块时,其__name__
变量将设置为__main__
。由于相对导入在层次结构中使用__name__
建立位置,这意味着没有目录信息可以解析以解决问题。这使得有意义的负担,我觉得以前没有意识到它是非常愚蠢的。
因此,开始了我的追求(是的,刚开始)。最后我了解了__package__
变量。在PEP笔记中阅读它,似乎它可以解决我所有的问题!所以我在bar_2
中的import语句之前尝试了以下样板代码:
if __name__ == '__main__':
__package__ = 'src.pkg_2'
这不起作用。 :(
我已经发现Guido已经解决了这个问题,他认为从一个包中运行一个模块作为反模式的整个概念。
查看此链接: https://mail.python.org/pipermail/python-3000/2007-April/006793.html
这是有道理的,因为我将是第一个承认我只是在进行即时测试的人......这绝不应该做! (对吗?)因此,据我所知,没有优雅的方法可以在一个包中运行模块,除非你做绝对导入...我想避免。
所以,在所有这些之后,我的问题是:我应该使用许多 hacky方法中的一种来解决这个问题并使用系统路径做不好的事情以便我可以有我的相关进口蛋糕并吃它(即通过运行__main__
进行试运行)?我想我已经知道了答案(我只想要一些干瘪的Yoda-like人来确认)。
可能的答案:
使用相对导入,因为硬编码路径(如果可以避免,通常是硬编码)是蚂蚁模式。
不要打扰运行嵌套在包中的模块__main__
...而是从测试模块运行它们(你先写的......对吗?)。
感谢您抽出宝贵时间阅读此问题。我意识到关于这个话题还有很多其他问题...但我想“分享我的旅程”,看看我目前的行动方案是否正确。
答案 0 :(得分:3)
在src/
中运行以下内容:
python -m pkg2.bar_2
将pkg2/bar_2.py
作为您的主要脚本,同时仍然在包中,这意味着相对导入将起作用。
答案 1 :(得分:1)
我遵循这些规则来防止任何问题:
当你不得不移动文件时,你很容易从相对导入中受益(当你这样做时,重命名一些导入行并不是那么重要。)
来自项目根目录的完整路径或使用文件磁盘上的完整路径可消除任何歧义,并将运行所需的文件。
答案 2 :(得分:1)
我的答案(以及许多CPython核心开发人员的答案)与Simeon的答案基本相同。唯一没有通过相对导入硬编码的是包的名称(在这种情况下是' src')。但无论如何,你在do_stuff.py中对它进行了硬编码。子包内相对导入(与您展示的跨子包导入不同)允许您将子包复制到另一个包(具有不同的名称),并可能在您执行此操作时更改子包名称。你真的需要那种特别的灵活性吗?它真的比相对进口的实际成本更有价值吗?
为什么子包,而不是把所有东西放在主包中,src? Idlelib在idlelib /本身中有大约60个运行时模块。对于测试模块,唯一的子包是idle_test。所有导入都以idlelib开头。
我非常喜欢通过运行非cli模块作为主模块来运行一个模块(而不是包测试套件)的测试。它鼓励增量TDD。所以如果名称那么我就有了...' idlelib中的运行时和测试模块中的子句。