显式相对导入的正确样板是什么?
What is the correct boilerplate for explicit relative imports?
在 PEP 366 - Main module explicit relative imports 中引入了模块范围变量 __package__
以允许在子模块中显式相对导入,摘录如下:
When the main module is specified by its filename, then the
__package__
attribute will be set to None
. 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 manipulates sys.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
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/
)?
正确的样板文件是 none,只需编写显式相对导入,如果有人试图 运行 将模块作为脚本或 sys.path
配置错误,则让异常转义:
from . import baz
PEP 366 中给出的样板只是为了表明提议的更改足以让用户直接执行*工作,如果他们真的想要的话,它不是'无意暗示让直接执行工作是一个好主意(它不是,这是一个坏主意,它几乎不可避免地会导致其他问题,即使使用 PEP 的样板也是如此)。
您提出的替代样板重现了 Python 2 中隐式相对导入引起的问题:"baz"
模块从 __main__
作为 baz
导入,但将在其他地方作为 "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
开关。
* 直接执行表示执行代码来自:
- 除目录和 zip 文件路径 (
python <file path>
) 之外的文件路径参数。
- 一个
-c
参数(python -c <code>
)。
- 交互式解释器(
python
)。
- 标准输入(
python < <file path>
)。
间接执行表示从以下位置执行代码:
- 目录或 zip 文件路径参数 (
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
?
除了使用现代导入系统的 __main__
模块外,很难在任何地方获得 __package__ is None
。但它过去更常见,因为 __package__
不是由导入系统在模块加载时设置,而是由模块中执行的第一个显式相对导入延迟设置。换句话说,样板只是试图让直接执行工作(上面的案例 1 到 4),但是 __package__ is None
用于 暗示直接执行 或导入statement(上面的案例 7),所以要过滤掉案例 7,子表达式 __name__ == "__main__"
(上面的案例 1 到 6)是必要的。
- Shouldn’t the second subexpression
__package__ is None
be not __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
的其他风格错误配置默默通过。
在 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
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/
)?
正确的样板文件是 none,只需编写显式相对导入,如果有人试图 运行 将模块作为脚本或 sys.path
配置错误,则让异常转义:
from . import baz
PEP 366 中给出的样板只是为了表明提议的更改足以让用户直接执行*工作,如果他们真的想要的话,它不是'无意暗示让直接执行工作是一个好主意(它不是,这是一个坏主意,它几乎不可避免地会导致其他问题,即使使用 PEP 的样板也是如此)。
您提出的替代样板重现了 Python 2 中隐式相对导入引起的问题:"baz"
模块从 __main__
作为 baz
导入,但将在其他地方作为 "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
开关。
* 直接执行表示执行代码来自:
- 除目录和 zip 文件路径 (
python <file path>
) 之外的文件路径参数。 - 一个
-c
参数(python -c <code>
)。 - 交互式解释器(
python
)。 - 标准输入(
python < <file path>
)。
间接执行表示从以下位置执行代码:
- 目录或 zip 文件路径参数 (
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
?
除了使用现代导入系统的 __main__
模块外,很难在任何地方获得 __package__ is None
。但它过去更常见,因为 __package__
不是由导入系统在模块加载时设置,而是由模块中执行的第一个显式相对导入延迟设置。换句话说,样板只是试图让直接执行工作(上面的案例 1 到 4),但是 __package__ is None
用于 暗示直接执行 或导入statement(上面的案例 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
的其他风格错误配置默默通过。