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

lai*_*e9m 32 python python-3.x

我想知道的究竟__package__是什么意思?在官方文件中没有找到任何解释,即使是在SO上.

如果你能提供一些例子,我会非常高兴.

Mar*_*ers 44

请参阅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将foo.bar两者的__name__和__package__属性.

  • Martijn,而不是你的回答,但是当`__package__` 属性真的有用时,你介意举一个问题的例子吗? (2认同)
  • @Andrei:我链接到的 PEP 专门解决了这个问题,但它允许您检测包中的模块文件何时直接执行(使用“python /modulename.py”)并针对这种情况进行调整。 (2认同)

Aar*_*all 20

我想知道的是究竟是什么__package__意思

它是实现显式相对导入的机制.

有三种可能的值类别 __package__

  • 包名(字符串)
  • 一个空字符串
  • 没有

包裹名字

也就是说,如果模块位于包中,__package__则将其设置为包名称以启用显式相对导入.特别:

当模块是包时,其__package__值应设置为它__name__.当模块不是包时,__package__应将子模块设置为父包的名称.

空字符串

如果模块位于root或top级别,即导入当前模块

import current_module
Run Code Online (Sandbox Code Playgroud)

或者当顶级模块作为入口点运行时,如下所示:

$ python -m current_module
Run Code Online (Sandbox Code Playgroud)

然后__package__是一个空字符串.或者正如文档所说:

当模块不是包时,__package__应将其设置为顶级模块的空字符串...

没有

如果模块/脚本由filename运行,__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')
Run Code Online (Sandbox Code Playgroud)

现在我们看到作为模块执行的foo.py有一个空字符串__package__,而由文件名作为入口点执行的脚本具有None:

$ python -m foo
__main__, __file__: ~\foo.py, __package__: ''
$ python foo.py
__main__, __file__: foo.py, __package__: None
Run Code Online (Sandbox Code Playgroud)

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

$ python -m package
package, __file__: ~\package\__init__.py, __package__: 'package'
__main__, __file__: ~\package\__main__.py, __package__: 'package'
Run Code Online (Sandbox Code Playgroud)

类似地,当我们执行子模块作为入口点的__init__.py模块时,模块运行,然后运行:

$ python -m package.bar
package, __file__: ~\package\__init__.py, __package__: 'package'
__main__, __file__: ~\package\bar.py, __package__: 'package'
Run Code Online (Sandbox Code Playgroud)

最后,我们看到显式相对导入,具有的完整原因__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'
Run Code Online (Sandbox Code Playgroud)

注意,在输出中,我已经替换~了父目录.