Python中"__package__"属性的目的是什么?

66
我想知道的是,__package__ 到底是什么意思?在官方文档和 Stack Overflow 上都没有找到解释。如果您能提供一些示例,我会非常高兴。
2个回答

77
请参见PEP 366导入系统参考文档
主要的修改是引入了一个新模块级别属性 __package__。如果存在此属性,则相对导入将基于此属性,而不是模块 __name__ 属性。
同时,
  • 模块的__package__属性应该被设置。它的值必须是一个字符串,但可以与其__name__的值相同。如果该属性设置为None或缺失,则导入系统会使用更合适的值进行填充。当模块是一个包时,其__package__值应该被设置为其__name__。当模块不是一个包时,对于顶级模块,__package__应该被设置为空字符串,对于子模块,应该设置为父包的名称。详见PEP 366
因此,对于位于foo/bar/baz.py的模块,__name__被设置为foo.bar.baz__package__被设置为foo.bar,而foo/bar/__init__.py将对__name____package__属性都设置为foo.bar

3
Martijn,谢谢你的回答,但您能否举个例子,说明在什么情况下__package__属性真正有用呢? - chiffa
2
@Andrei:我提供的PEP专门解决了这个问题,它允许你检测包中的模块文件是否直接执行(使用python /modulename.py),并针对该情况进行调整。 - Martijn Pieters
我正在阅读PEP,但并不完全理解它的使用方式,主要是因为__name__.rpartition('.')[0]行的默认行为对我来说并不完全清晰,就像__name____package__属性的交互以及用于从项目中的其他文件夹导入模块的基本路径一样。实际上,我有一个可以访问主入口点的模块行为,而且我很满意,我不想破坏它,但我也想使用深层文件夹树中的模块作为某些应用程序的主要访问点,这是我目前遇到的问题。 - chiffa
1
str.rpartition() 调用会在最后一个 . 上分割并返回前半部分;因此 foo.bar.baz.eggs 分割成了 foo.bar.baz。其余内容太广泛,无法在注释中详细说明。 - Martijn Pieters

52

我只想知道__package__到底是什么意思

__package__是使显式相对导入成为可能的机制。

__package__有三种可能的取值:

  • 包名(字符串)
  • 空字符串
  • None

包名

也就是说,如果一个模块在一个包中,__package__将被设置为包名以启用显式相对导入。具体来说:

当模块是一个包时,应该将其__package__值设置为其__name__。当模块不是一个包时,__package__应该被设置为[...]对于子模块,则是父包的名称。

空字符串

如果一个模块在根目录或顶级目录下,即当前模块是使用

import current_module

或者当一个顶层模块被作为入口点运行时,例如:

$ python -m current_module

那么如果模块不是一个包,__package__将会是空字符串。或者如文档所说:

当模块不是一个包时,__package__应该对于顶层模块被设置为空字符串...

None

如果一个模块/脚本是通过文件名运行的,__package__为 None:

当主模块通过它的文件名指定时,__package__属性将会被设置成None。

证据

首先,让我们使用Python 3.6创建一个带有嘈杂调试信息的文件结构:

text = "print(f'{__name__}, __file__: {__file__}, __package__: {repr(__package__)}')"

from pathlib import Path
Path('foo.py').write_text(text)
Path('package').mkdir()
Path('package/__init__.py').write_text(text)
Path('package/__main__.py').write_text(text)
Path('package/bar.py').write_text(text)

# and include a submodule with a relative import:
Path('package/baz.py').write_text(text + '\nfrom . import bar')

现在我们可以看到,作为模块执行的 foo.py 的 __package__ 为空字符串,而以文件名为入口点执行的脚本却是 None

$ python -m foo
__main__, __file__: ~\foo.py, __package__: ''
$ python foo.py
__main__, __file__: foo.py, __package__: None

当我们将一个包作为入口点执行时,它的 __init__.py 模块会运行,然后它的 __main__.py 会运行:

$ python -m package
package, __file__: ~\package\__init__.py, __package__: 'package'
__main__, __file__: ~\package\__main__.py, __package__: 'package'

同样地,当我们将一个子模块作为入口点执行为模块时,__init__.py 模块会运行,然后它运行:

$ python -m package.bar
package, __file__: ~\package\__init__.py, __package__: 'package'
__main__, __file__: ~\package\bar.py, __package__: 'package'

最后,我们看到显式相对导入,也就是拥有__package__的全部原因(在这里发生的最后一步)被启用:

$ python -m package.baz
package, __file__: ~\package\__init__.py, __package__: 'package'
__main__, __file__: ~\package\baz.py, __package__: 'package'
package.bar, __file__: ~\package\bar.py, __package__: 'package'

注意,在输出中,我已经用~代替了父目录。


我猜一个小怪癖是这样称呼一个包:python package。在这种情况下,__package__是''而不是None。但我不知道为什么会做出这个选择。 - figs_and_nuts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接