2

假设我有以下结构:

app/
   __init__.py
   mod.py

   pkg/
      __init__.py
      submod.py

其中模块submod对 具有相对导入mod,即:

from .. import mod

我知道如果我想submod作为脚本执行,我可以执行以下操作app/

python -m pkg.submod

但我想submod.py成为一个可执行模块,我可以从系统中的任何地方调用它

python /path/to/submod.py

我认为PEP-366解决了这个问题,即我认为在我执行任何相关导入之前添加以下样板代码submod

if __name__ == "__main__" and __package__ is None:
    __package__ = "app.pkg"

然后我可以回到常规python /path/to/submod.py。但是,当我这样做时,我得到:

SystemError: Parent module 'app' not loaded, cannot perform relative import

为什么?

最后,我知道一种解决方案是进行操作sys.pathsubmod以便它可以看到mod1然后执行常规import mod1并避免相对导入。但是作为这个问题所示,这是危险的,因为对一个模块中的任何更改sys.path都会传播到其他所有模块,所以一般来说,篡改sys.path.

有没有什么办法:

  • 支持常规python /path/to/submod.py的相对导入调用

或者

  • 能够使用python /path/to/submod.py执行我的模块而无需篡改sys.pathPYTHONPATH

?

4

2 回答 2

3

正如 PEP 366 在您复制的样板文件之后明确指出的那样:

请注意,只有当顶层包已经可以通过sys.path. 需要额外的操作代码sys.path才能直接执行,而无需导入顶级包。

因此,假设您的代码如下所示:

if __name__ == "__main__" and __package__ is None:
    __package__ = "app.pkg"
from .. import mod

你应该得到一个像这样的异常:

SystemError: Parent module 'app' not loaded, cannot perform relative import

正如错误消息所暗示的那样,您需要先以app某种方式导入,然后才能进行任何相对导入。这显然意味着你不能 relative-import app,所以你需要绝对导入它。您可以通过各种骇人听闻的方式来做到这一点,或者您可以按照 PEP 的建议去做,并sys.path解决所有暗示的问题。但否则,你不能这样做。


这只是尝试从包中间运行脚本很困难的多种原因之一。这是故意的;如PEP 3122中所述:

Guido 将包中运行的脚本视为反模式。

有两种方法可以实现您想要的。首先,您已经开始工作:只需将模块作为模块而不是脚本运行。(让这项工作发挥作用是 PEP 366 的主要目的。)另一个是将模块和脚本分成两部分。只需编写一个包装器,您就可以轻松地做到这一点。

例如,假设submod.py看起来像这个愚蠢的玩具示例:

if __name__ == "__main__" and __package__ is None:
    __package__ = "app.pkg"
from .. import mod

def main(argv):
    print(argv)

if __name__ == '__main__':
    import sys
    main(sys.argv)

创建一个新文件,比如说,sub.py作为包的兄弟:

import sys
import app.pkg.submod
app.pkg.submod.main(sys.argv)

现在,您可以sub.py从系统上的任何位置运行。正如sys.path所说:

在程序启动时初始化时,此列表的第一项 path[0] 是包含用于调用 Python 解释器的脚本的目录。

由于该目录也是该目录app所在的目录,因此可以保证您能够进行 absolute-import app.pkg.submod,这意味着submod将能够相对导入它想要的任何内容。

于 2013-08-19T21:48:05.527 回答
1

包裹不是pkg2;它是app.pkg2。相应地设置__package__

if __name__ = '__main__' and __package__ is None:
    __package__ = 'app.pkg2'

如果这不是问题,那么app可能不在路径上。我所知道的最好的解决方案是将它放在路径上(通过移动app或将其当前目录放在 PYTHONPATH 中),但是如果您正在寻找一种让模块app相互看到而不被外部模块看到的方法,我不什么都不知道 似乎它可能很有用。

于 2013-08-19T21:17:29.563 回答