在 PEP 366 - Main module explicit relative imports其中引入了模块范围变量 __package__
为了允许子模块中的显式相对导入,有以下摘录:
When the main module is specified by its filename, then the
__package__
attribute will be set toNone
. To allow relative imports when the module is executed directly, boilerplate similar to the following would be needed before the first relative import statement:if __name__ == "__main__" and __package__ is None: __package__ = "expected.package.name"
Note that this boilerplate is sufficient only if the top level package is already accessible via
sys.path
. Additional code that manipulatessys.path
would be needed in order for direct execution to work without the top level package already being importable.This approach also has the same disadvantage as the use of absolute imports of sibling modules - if the script is moved to a different package or subpackage, the boilerplate will need to be updated manually. It has the advantage that this change need only be made once per file, regardless of the number of relative imports.
我尝试在以下设置中使用此样板:
foo
├── bar.py
└── baz.py
if __name__ == "__main__" and __package__ is None:
__package__ = "foo"
from . import baz
当从文件系统执行子模块 bar.py 时样板工作(
PYTHONPATH
修改使包 foo/可在 sys.path
上访问):PYTHONPATH=$(pwd) python3 foo/bar.py
当从模块命名空间执行子模块 bar.py 时,样板也可以工作:python3 -m foo.bar
但是,以下替代样板在这两种情况下都与 bar.py 子模块的内容一样有效:if __package__:
from . import baz
else:
import baz
此外,这个替代样板更简单,当它与子模块 baz.py 一起移动到不同的包时,不需要对子模块 bar.py 进行任何更新(因为它没有硬编码包名 "foo"
)。所以这是我关于 PEP 366 样板的问题:
__name__ == "__main__"
必要的还是第二个子表达式 __package__ is None
已经暗示了? ? __package__ is None
是 not __package__
相反,为了处理 __package__
的情况是空字符串(如 __main__.py
通过提供包含目录从文件系统执行的子模块: PYTHONPATH=$(pwd) python3 foo/
)? 最佳答案
正确的样板文件是无,只需编写显式的相对导入,如果有人试图将模块作为脚本运行或有 sys.path
则让异常逃逸配置错误:
from . import baz
PEP 366 中给出的样板只是为了表明提议的更改足以允许用户在他们真正想要的情况下使直接执行*工作,这并不意味着使直接执行工作是一个好主意(它不是,这是一个坏主意,几乎不可避免地会导致其他问题,即使使用 PEP 的样板文件也是如此)。您提出的替代样板重新创建了由 Python 2 中的隐式相对导入引起的问题:
"baz"
模块被导入为 baz
来自 __main__
, 但将作为 "foo.baz"
导入其他任何地方,所以你最终会在 sys.modules
中得到两个副本在不同的名称下。除其他问题外,这意味着如果某些其他模块抛出
foo.baz.SomeException
和你的__main__
模块试图捕捉 baz.SomeException
,它不起作用,因为它们将是来自两个不同模块的两个不同异常对象。相比之下,如果您使用 PEP 样板,那么
__main__
将正确导入 baz
如"foo.baz"
,而您唯一需要担心的是其他模块可能会导入 foo.bar
.如果您想要更简单的样板来明确防止“无意中以不同的名称制作同一模块的两个副本”错误而不对包名称进行硬编码,那么您可以使用它:
if not __package__:
raise RuntimeError(f"{__file__} must be imported as a package submodule")
但是,如果你要这样做,你也可以这样做 from . import baz
无条件地按照上面的建议,如果有人试图直接运行脚本而不是通过 -m
运行脚本,则让底层异常逃逸。转变。* 直接执行意味着从以下位置执行代码:
python <file path>
)。 -c
论点(python -c <code>
)。 python
)。 python < <file path>
)。 间接执行意味着从以下位置执行代码:
python <directory or zip file path>
)。 -m
论点(python -m <module name>
)。 import <module name>
)现在具体回答你的问题:
- Is the first subexpression
__name__ == "__main__"
necessary or is it already implied by the second subexpression__package__ is None
?
很难得到
__package__ is None
__main__
以外的任何地方具有现代导入系统的模块。但它过去更常见,而不是由导入系统在模块加载时设置,__package__
而是由模块中执行的第一个显式相对导入延迟设置。换句话说,样板只是试图让直接执行工作(上面的案例 1 到 4)但是 __package__ is None
用于暗示直接执行或导入语句(上面的案例 7),因此要过滤掉案例 7 子表达式 __name__ == "__main__"
(上述案例 1 至 6)是必要的。
- Shouldn’t the second subexpression
__package__ is None
benot __package__
instead, in order to handle the case where__package__
is the empty string (like in a__main__.py
submodule executed from the file system by supplying the containing directory:PYTHONPATH=$(pwd) python3 foo/
)?
不,因为样板只是试图让直接执行工作(上面的案例 1 到 4),它没有试图让
sys.path
的其他风格错误配置静默通过。
关于python - 显式相对导入的正确样板是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63909243/