问题描述
在PEP 366 - Main module explicit relative imports中引入了模块作用域变量__package__
,以允许在子模块中进行显式相对导入,其中摘录如下:
当主模块由其文件名指定时,则
__package__
属性将设置为None
。为了在直接执行模块时允许相对导入,样板类似于 在第一个相对导入语句之前,需要执行以下操作:if __name__ == "__main__" and __package__ is None: __package__ = "expected.package.name"
请注意,仅当已经可以通过以下方式访问顶层软件包时,此样板才足够
sys.path
。在其中需要操纵sys.path
的其他代码 在没有顶级软件包的情况下直接执行命令 已经可以导入。此方法也具有与使用绝对值相同的缺点 导入同级模块-如果脚本移到其他位置 包或子包,样板将需要更新 手动。这样做的好处是只需要进行一次更改 每个文件,无论相对导入的数量如何。
我尝试在以下设置中使用此样板:
-
目录布局:
foo ├── bar.py └── baz.py
-
bar.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__
的形式导入,但将以{{1}的形式导入},因此您最终在"foo.baz"
中得到了两个副本,名称不同。
在其他问题中,这意味着如果其他某个模块抛出sys.modules
,而您的foo.baz.SomeException
模块试图捕获__main__
,则该模块将不起作用,因为这将是两个不同的异常来自两个不同模块的对象。
相比之下,如果您使用PEP样板,则baz.SomeException
会将__main__
正确地导入为baz
,而您唯一需要担心的是其他可能导入{{ 1}}。
如果您希望使用更简单的样板来明确地防止“无意间以不同的名称制作同一模块的两个副本”错误而无需对软件包名称进行硬编码,则可以使用以下方法:
"foo.baz"
但是,如果您要这样做,则可以像上面建议的那样无条件执行foo.bar
,并且如果有人尝试直接运行脚本而不是通过{{1 }}开关。
* 直接执行表示从以下位置执行代码:
- 除了目录和zip文件路径(
if not __package__: raise RuntimeError(f"{__file__} must be imported as a package submodule")
)以外的文件路径参数。 - 一个
from . import baz
自变量(-m
)。 - 交互式解释器(
python <file path>
)。 - 标准输入(
-c
)。
但不是来自:
- 目录或zip文件路径参数(
python -c <code>
)。 - 一个
python
自变量(python < <file path>
)。 - 导入语句(
python <directory or zip file path>
)
现在回答您的问题:
- 第一个子表达式
-m
是必需的还是第二个子表达式python -m <module name>
已经隐含了?
使用现代导入系统很难获得import <module name>
模块之外的__name__ == "__main__"
模块。但是它曾经更常见,因为__package__ is None
不是由导入系统在模块加载时设置的,而是由模块中执行的第一个显式相对导入来延迟设置的。换句话说,样板仅试图让直接执行工作(上述情况1至4),但是__package__ is None
用于暗示直接执行或import语句 (上述情况7),因此要过滤出情况7,需要使用子表达式__main__
(上述情况1至6)。
- 第二个子表达式
__package__
不应为__package__ is None
,以便处理__name__ == "__main__"
为空字符串的情况(如在__package__ is None
子模块中执行 通过提供包含目录的文件系统:not __package__
)?
样板仅试图让直接执行工作(上述情况1至4),而不是试图让__package__
错误配置的其他形式静默地通过。