Python 2.5中的相对导入

19
我知道在Python中有很多关于相同导入问题的问题,但似乎没有人能够提供一个清晰的正确用法示例。
假设我们有一个名为“mypackage”的包,其中包含两个模块“foo”和“bar”。在“foo”中,我们需要能够访问“bar”。
由于我们仍在开发中,“mypackage”不在“sys.path”中。
我们希望能够:
- 导入“mypackage.foo” - 将“foo.py”作为脚本运行,并执行来自“__main__”部分的示例用法或测试。 - 使用Python 2.5
为了确保在所有这些情况下都能正常工作,我们在foo.py中应该如何进行导入?
# mypackage/__init__.py
...

# mypackage/foo/__init__.py
...

# mypackage/bar.py
def doBar()
    print("doBar")

# mypackage/foo/foo.py
import bar # Fails with module not found
import .bar # Fails due to ValueError: Attempted relative import in non-package

def doFoo():
    print(doBar())

if __name__ == '__main__':
    doFoo()

我认为你必须区分可导入的模块和脚本。虽然我不确定我是否同意这种设计,但这是我的理解。 - Thomas K
通常情况下,如果您需要从上一级导入包,则可能存在问题:您是否希望将bar作为库而不是作为mypackage的子模块使用?也就是说,您是否可以将mypackage重构为mypackage1mypackage2,其中mypackage2(具有foo)导入mypackage1(具有bar)? - jsalonen
2个回答

32

请参考来自PEP 328的以下信息:

相对导入使用模块的__name__属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,设置为'__main__'),则相对导入将被解析为顶级模块,而不管该模块实际上位于文件系统的哪个位置。

当您将foo.py作为脚本运行时,该模块的__name__'__main__',因此您无法进行相对导入。即使mypackagesys.path上,这也是真实的情况。基本上,只有在导入了该模块的情况下,才能从模块进行相对导入。

以下是几种解决方法:

1) 在foo.py中,检查__name__ == '__main__'并有条件地将mypackage添加到sys.path中:

if __name__ == '__main__':
    import os, sys
    # get an absolute path to the directory that contains mypackage
    foo_dir = os.path.dirname(os.path.join(os.getcwd(), __file__))
    sys.path.append(os.path.normpath(os.path.join(foo_dir, '..', '..')))
    from mypackage import bar
else:
    from .. import bar

2) 始终使用 from mypackage import bar 导入 bar,并以这样的方式执行 foo.py,使得 mypackage 自动可见:

$ cd <path containing mypackage>
$ python -m mypackage.foo.foo

谢谢!唯一仍不清楚的是检测包路径的问题。我明天会检查并带回结果。 - sorin
@sorin - 我刚刚编辑了我的答案,这样你就可以在任何地方调用foo.py时获取到mypackage的路径。 - Andrew Clark
在Windows上,我发现getcwd()未提供脚本目录的情况很少。至少有3种调用Python脚本的方式:“python a.py”、“a.py”以及从Windows资源管理器中运行(最终通过pythonw)。 - sorin
@sorin - 很遗憾,我只能在Linux上进行测试,我看到的行为是__file__要么是绝对路径,要么是从os.getcwd()foo.py的路径。在这两种情况下,foo_dir都是包含foo.py的目录的绝对路径。如果在Windows上不是这种情况,我不确定它需要如何更改。 - Andrew Clark
@fj 我发现你的方法在 foo.py 中需要使用 bar 中的内容时无法正常工作,请查看更新后的示例。 - sorin
1
@sorin - 如果你需要从bar中使用doBar,你需要通过from mypackage.bar import doBar将其导入到foo的命名空间中,或者使用from mypackage import bar并调用bar.doBar() - Andrew Clark

7

我的解决方案看起来更加简洁,并且可以放在顶部和其他导入模块一起:

try:
   from foo import FooClass
except ModuleNotFoundError:
   from .foo import FooClass

谢谢你的回答!它确实帮了我很多! - zl2003cn

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