在Python 2和3中,导入结构既可以在包中也可以在包中运行?

otu*_*tus 6 python python-2.x package python-import python-3.x

当我开发纯Python 2的包时,我可以使用普通import b语法导入相对路径,而不关心导入文件是否在包中.这样做的好处是我可以if __name__ == "__main__":通过执行文件来运行任何文件的块,并且所有导入都可以正常工作.

添加Python 3支持后,我不得不转向新的相对导入语法,2.7也支持from . import b.但是,此语法适用于包内.直接直接执行文件不再有效:

Traceback (most recent call last):
  File "./a.py", line 2, in <module>
    from . import b
ValueError: Attempted relative import in non-package
Run Code Online (Sandbox Code Playgroud)

解决方法是通过从上层目录将其作为模块导入来调用该文件:

python -m foo.a
Run Code Online (Sandbox Code Playgroud)

但是,这会对工作目录提出一个要求,这会阻止您将输出传递给其他同样关心工作目录的程序.

有没有办法让你的蛋糕和它吃?即支持作为脚本运行和作为包的一部分导入,同时在Python 2和3中工作?


示例包结构:

foo/
foo/__init__.py
foo/a.py (imports b)
foo/b.py (imports c)
foo/c.py
Run Code Online (Sandbox Code Playgroud)

我希望以下两个都适用于(a,b,c)中的x:

import foo.x (in some file when foo/ is in path)

python[23] path/to/foo/x.py
Run Code Online (Sandbox Code Playgroud)

下面的评论提到__package__根据PEP 366设置,但"如果脚本被移动到不同的包或子包,则需要手动更新样板."

更新:我试图让PEP 366解决方案正常工作,但无法弄明白.它说:

sys.path需要操作的附加代码才能使直接执行工作而顶级包不可导入.

从非导入包执行文件时就是这种情况.那些额外的代码会是什么样的?

otu*_*tus 0

这是一个基于KronoS 的答案和评论的解决方案,无论路径或包名称如何,都允许模块使用相同的样板:

if __name__ == "__main__" and __package__ == None:
    import importlib
    import os.path
    import sys
    def _gen_path():
        head, tail = os.path.split(os.path.realpath(__file__))
        while head:
            if not os.path.isfile(os.path.join(head, '__init__.py')):
                yield head
                return
            head, tail = os.path.split(head)
            yield tail
    def _load_package():
        path = list(_gen_path())
        syspath = sys.path[:]
        sys.path[:0] = [path.pop()]
        package = '.'.join(reversed(path))
        importlib.import_module(package)
        sys.path = syspath
        return package
    __package__ = _load_package()
Run Code Online (Sandbox Code Playgroud)

它沿着文件路径向上遍历直到存在__init__.py标记包的文件,然后导入模块的父包,并__package__正确设置。之后,相对导入就可以from ..bar import baz正常工作了。


太糟糕了,将这些函数放在自己的模块中会让您回到原点。另外,似乎没有一种 Python 2/3 可移植的方法来限制sys.path更改对导入的影响,因此基目录中的任何内容都可能会隐藏与父包一起导入的任何模块或包中的绝对导入。