ChatGPT解决这个技术问题 Extra ChatGPT

__all__ 在 Python 中是什么意思?

我在 __init__.py 个文件中看到 __all__。它有什么作用?


B
Boris Verkhovskiy

链接到但此处未明确提及的正是使用 __all__ 的时间。它是一个字符串列表,定义在模块上使用 from <module> import * 时将导出模块中的哪些符号。

例如,foo.py 中的以下代码显式导出符号 barbaz

__all__ = ['bar', 'baz']

waz = 5
bar = 10
def baz(): return 'baz'

然后可以像这样导入这些符号:

from foo import *

print(bar)
print(baz)

# The following will trigger an exception, as "waz" is not exported by the module
print(waz)

如果上面的 __all__ 被注释掉,则此代码将执行完成,因为 import * 的默认行为是从给定的命名空间导入所有不以下划线开头的符号。

参考:https://docs.python.org/tutorial/modules.html#importing-from-a-package

注意: __all__ 仅影响 from <module> import * 行为。 __all__ 中未提及的成员仍然可以从模块外部访问,并且可以使用 from <module> import <member> 导入。


我们不应该将 baz 打印为 print(baz()) 吗?
@JohnCole baz 是函数对象, baz() 将运行函数对象
目的是说明符号已导出。它是否执行功能是次要的。
我觉得令人费解的是,直到今天,还没有办法通过直接引用函数/对象来填充 __all__。相反,我们必须在名称更改时输入他们的名字并单独更正它们。对于活动代码库来说似乎很容易出错。
@JulioCezarSilva 有点离题,但值得注意的是,对于类和函数,您可以使用 __name__ 属性
J
Jimmy

它是该模块的公共对象列表,由 import * 解释。它覆盖了隐藏以下划线开头的所有内容的默认设置。


以下划线开头的对象,或者如果存在 __all__,则在 __all__ 中未提及的对象并未完全隐藏;如果您知道它们的名字,就可以正常查看和访问它们。只有在“import *”的情况下,无论如何都不推荐,这种区别才有意义。
@BrandonRhodes:这也不完全正确:建议仅导入您知道为 import * 设计的模块(例如 tk)。如果是这种情况,一个很好的提示是模块代码中存在 __all__ 或以下划线开头的名称。
公共和内部接口 - python.org/dev/peps/pep-0008/#id50,为了更好地支持自省,模块应使用 __all__ 属性在其公共 API 中显式声明名称。将 __all__ 设置为空列表表示该模块没有公共 API。
我不确定如果今天(或者甚至在 2012 年)发布了 tk,推荐的做法是使用 from tk import *。我认为这种做法是由于惯性而不是故意设计而被接受的。
总结一下:如果您有 __all__import * 将导入 __all__ 中的所有内容,否则,它将导入不以下划线开头的所有内容。
R
Russia Must Remove Putin

用 Python 解释一切?我不断看到变量 __all__ 设置在不同的 __init__.py 文件中。这是做什么的?

__all__ 做什么?

它从模块中声明语义上的“公共”名称。如果 __all__ 中有名称,则预计用户会使用它,并且他们可以期望它不会改变。

它还将产生程序化效果:

进口 *

__all__ 在模块中,例如 module.py

__all__ = ['foo', 'Bar']

意味着当您从模块中 import * 时,只会导入 __all__ 中的那些名称:

from module import *               # imports foo and Bar

文档工具

文档和代码自动完成工具可能(实际上应该)还检查 __all__ 以确定哪些名称显示为可从模块中获得。

__init__.py 使目录成为 Python 包

docs

需要 __init__.py 文件才能使 Python 将目录视为包含包;这样做是为了防止具有通用名称(例如字符串)的目录无意中隐藏了稍后出现在模块搜索路径上的有效模块。

在最简单的情况下,__init__.py 可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__ 变量。

因此,__init__.py 可以为 声明 __all__

管理 API:

包通常由可以相互导入但必须与 __init__.py 文件绑定在一起的模块组成。该文件使该目录成为实际的 Python 包。例如,假设您在一个包中有以下文件:

package
├── __init__.py
├── module_1.py
└── module_2.py

让我们使用 Python 创建这些文件,以便您可以继续进行操作 - 您可以将以下内容粘贴到 Python 3 shell 中:

from pathlib import Path

package = Path('package')
package.mkdir()

(package / '__init__.py').write_text("""
from .module_1 import *
from .module_2 import *
""")

package_module_1 = package / 'module_1.py'
package_module_1.write_text("""
__all__ = ['foo']
imp_detail1 = imp_detail2 = imp_detail3 = None
def foo(): pass
""")

package_module_2 = package / 'module_2.py'
package_module_2.write_text("""
__all__ = ['Bar']
imp_detail1 = imp_detail2 = imp_detail3 = None
class Bar: pass
""")

现在您已经提供了一个完整的 api,其他人在导入您的包时可以使用它,如下所示:

import package
package.foo()
package.Bar()

并且该包不会包含您在创建模块时使用的所有其他实现细节,这些模块会使 package 命名空间变得混乱。

__init__.py 中的 __all__

经过更多的工作,也许您已经决定模块太大(比如数千行?)并且需要拆分。因此,您执行以下操作:

package
├── __init__.py
├── module_1
│   ├── foo_implementation.py
│   └── __init__.py
└── module_2
    ├── Bar_implementation.py
    └── __init__.py

首先使子包目录与模块同名:

subpackage_1 = package / 'module_1'
subpackage_1.mkdir()
subpackage_2 = package / 'module_2'
subpackage_2.mkdir()

移动实现:

package_module_1.rename(subpackage_1 / 'foo_implementation.py')
package_module_2.rename(subpackage_2 / 'Bar_implementation.py')

为每个声明 __all__ 的子包创建 __init__.py

(subpackage_1 / '__init__.py').write_text("""
from .foo_implementation import *
__all__ = ['foo']
""")
(subpackage_2 / '__init__.py').write_text("""
from .Bar_implementation import *
__all__ = ['Bar']
""")

现在您仍然在包级别配置了 api:

>>> import package
>>> package.foo()
>>> package.Bar()
<package.module_2.Bar_implementation.Bar object at 0x7f0c2349d210>

而且您可以轻松地向您的 API 添加可以在子包级别而不是子包的模块级别管理的内容。如果您想为 API 添加新名称,只需更新 __init__.py,例如在 module_2 中:

from .Bar_implementation import *
from .Baz_implementation import *
__all__ = ['Bar', 'Baz']

如果您还没有准备好在顶级 API 中发布 Baz,那么您可以在顶级 __init__.py 中:

from .module_1 import *       # also constrained by __all__'s
from .module_2 import *       # in the __init__.py's
__all__ = ['foo', 'Bar']     # further constraining the names advertised

如果您的用户知道 Baz 的可用性,他们可以使用它:

import package
package.Baz()

但如果他们不知道,其他工具(如 pydoc)不会通知他们。

您可以稍后在 Baz 准备好进入黄金时段时更改它:

from .module_1 import *
from .module_2 import *
__all__ = ['foo', 'Bar', 'Baz']

前缀 _ 与 __all__:

默认情况下,当使用 import * 导入时,Python 将导出所有不以 _ 开头的名称。正如此处的 shell 会话所示,import * 不会从 us.py 模块中引入 _us_non_public 名称:

$ cat us.py
USALLCAPS = "all caps"
us_snake_case = "snake_case"
_us_non_public = "shouldn't import"
$ python
Python 3.10.0 (default, Oct  4 2021, 17:55:55) [GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from us import *
>>> dir()
['USALLCAPS', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'us_snake_case']

您当然可以依赖这种机制。 Python 标准库中的一些包,实际上 确实 依赖于此,但要这样做,它们会为其导入设置别名,例如,在 ctypes/__init__.py 中:

import os as _os, sys as _sys

使用 _ 约定可以更优雅,因为它消除了再次命名名称的冗余。但它增加了导入的冗余(如果你有很多),并且很容易忘记始终如一地这样做 - 而你想要的最后一件事是必须无限期地支持你想要的东西只是一个实现细节,只是因为您在命名函数时忘记了 _ 前缀。

我个人在模块开发生命周期的早期编写了 __all__,以便可能使用我的代码的其他人知道他们应该使用和不使用什么。

标准库中的大多数包也使用 __all__

当避免 __all__ 是有道理的

在以下情况下,坚持使用 _ 前缀约定代替 __all__ 是有意义的:

您仍处于早期开发模式并且没有用户,并且不断地调整您的 API。

也许您确实有用户,但是您有涵盖 API 的单元测试,并且您仍在积极地添加到 API 并在开发中进行调整。

出口装饰器

使用 __all__ 的缺点是您必须编写两次导出的函数和类的名称 - 并且信息与定义分开。我们可以使用装饰器来解决这个问题。

我从 David Beazley 关于包装的演讲中得到了这样一个出口装饰器的想法。这个实现似乎在 CPython 的传统导入器中运行良好。如果你有一个特殊的导入钩子或系统,我不保证,但如果你采用它,退出是相当简单的 - 你只需要手动将名称添加回 __all__

因此,例如,在实用程序库中,您将定义装饰器:

import sys

def export(fn):
    mod = sys.modules[fn.__module__]
    if hasattr(mod, '__all__'):
        mod.__all__.append(fn.__name__)
    else:
        mod.__all__ = [fn.__name__]
    return fn

然后,在您要定义 __all__ 的地方,执行以下操作:

$ cat > main.py
from lib import export
__all__ = [] # optional - we create a list if __all__ is not there.

@export
def foo(): pass

@export
def bar():
    'bar'

def main():
    print('main')

if __name__ == '__main__':
    main()

无论是作为 main 运行还是由另一个函数导入,这都可以正常工作。

$ cat > run.py
import main
main.main()

$ python run.py
main

使用 import * 进行 API 配置也可以:

$ cat > run.py
from main import *
foo()
bar()
main() # expected to error here, not exported

$ python run.py
Traceback (most recent call last):
  File "run.py", line 4, in <module>
    main() # expected to error here, not exported
NameError: name 'main' is not defined

交叉引用:关于如何编写 @export 装饰器的问题,我在 this CW answer 中提到了您的装饰器。
在帮助一个相对较新的 Python 开发人员了解使用 __init__.py 导入模块/包的过程以及使用 __all__ 方面,这是我所见过的最有用的答案
这对我有很大帮助。我的问题是,我要导入的子模块都是生成的文件,它们的符号中有很多我想去掉的杂物,而不必手动确保 __all__ 是正确的。
@MikeC 那么也许你也应该生成你的 __all__ - 但是我会说你有一个不稳定的 API ......这将是一些全面的验收测试。
@AaronHall“他们不会有所有其他的名字......把包命名空间弄得一团糟”但是他们有名字 module_1module_2;可以在 __init__.py 中包含显式 del module_1 吗?我认为这是值得的吗?
M
MartinStettner

为了准确起见,我只是添加了这个:

所有其他答案均参考 modules。原始问题在 __init__.py 文件中明确提到了 __all__,所以这是关于 python packages

通常,只有在使用 import 语句的 from xxx import * 变体时,__all__ 才会发挥作用。这适用于包和模块。

其他答案中解释了模块的行为。 here 详细描述了包的确切行为。

简而言之,包级别的 __all__ 与模块的作用大致相同,只是它处理包中的 模块(与在模块中指定 名称 不同) .因此,__all__ 指定了在我们使用 from package import * 时应加载并导入当前命名空间的所有模块。

最大的区别是,当您省略包的 __init__.py 中的 __all__ 声明时,语句 from package import * 将不会导入任何内容(文档中解释了例外情况,请参阅链接以上)。

另一方面,如果您在模块中省略 __all__,“星号导入”将导入模块中定义的所有名称(不以下划线开头)。


from package import * 仍将导入 __init__.py 中定义的所有内容,即使没有 all。重要的区别是没有 __all__ 它不会自动导入包目录中定义的任何模块。
当 all 包含 [foo, bar] 并且在 test.py 文件中如果我们使用:from package import *,那么 foo 和 bar 是导入到 test.py 的本地命名空间中还是导入到 foo 和 bar 自己的命名空间中?
L
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

它还改变了 pydoc 将显示的内容:

模块1.py

a = "A"
b = "B"
c = "C"

模块2.py

__all__ = ['a', 'b']

a = "A"
b = "B"
c = "C"

$ pydoc模块1

Help on module module1:

NAME
    module1

FILE
    module1.py

DATA
    a = 'A'
    b = 'B'
    c = 'C'

$ pydoc模块2

Help on module module2:

NAME
    module2

FILE
    module2.py

DATA
    __all__ = ['a', 'b']
    a = 'A'
    b = 'B'

我在我的所有模块中声明了 __all__,并强调了内部细节,这在使用您以前在现场口译会话中从未使用过的东西时真的很有帮助。


K
Kye

__all__from <module> import *from <package> import * 中自定义 *

module 是要导入的 .py 文件。

package 是包含 __init__.py 文件的目录。一个包通常包含模块。

模块

""" cheese.py - an example module """

__all__ = ['swiss', 'cheddar']

swiss = 4.99
cheddar = 3.99
gouda = 10.99

__all__ 让人们了解模块的“公共”功能。[@AaronHall]此外,pydoc 可以识别它们。[@Longpoke]

从模块导入 *

查看如何将 swisscheddar 带入本地命名空间,而不是 gouda

>>> from cheese import *
>>> swiss, cheddar
(4.99, 3.99)
>>> gouda
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'gouda' is not defined

如果没有 __all__,任何符号(不以下划线开头)都将可用。

不带 * 的导入不受 __all__ 影响

导入模块

>>> import cheese
>>> cheese.swiss, cheese.cheddar, cheese.gouda
(4.99, 3.99, 10.99)

从模块导入名称

>>> from cheese import swiss, cheddar, gouda
>>> swiss, cheddar, gouda
(4.99, 3.99, 10.99)

导入模块作为本地名称

>>> import cheese as ch
>>> ch.swiss, ch.cheddar, ch.gouda
(4.99, 3.99, 10.99)

包裹

package__init__.py 文件中,__all__ 是带有公共模块或其他对象名称的字符串列表。这些功能可用于通配符导入。与模块一样,当从包导入通配符时,__all__ 会自定义 *[@MartinStettner]

以下是 Python MySQL Connector __init__.py 的摘录:

__all__ = [
    'MySQLConnection', 'Connect', 'custom_error_exception',

    # Some useful constants
    'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
    'HAVE_CEXT',

    # Error handling
    'Error', 'Warning',

    ...etc...

    ]

默认情况 asterisk with no __all__ for a package 很复杂,因为明显的行为代价高昂:使用文件系统搜索包中的所有模块。相反,在我阅读文档时,只导入了 __init__.py 中定义的对象:

如果 __all__ 没有定义,则 from sound.effects import * 语句不会将 sound.effects 包中的所有子模块导入当前命名空间;它只确保包 sound.effects 已被导入(可能在 __init__.py 中运行任何初始化代码),然后导入包中定义的任何名称。这包括由 __init__.py 定义的任何名称(以及显式加载的子模块)。它还包括先前导入语句显式加载的包的任何子模块。

最后,一个受人尊敬的堆栈溢出答案、教授和随处可见的人的传统,是首先提出问题的责备:

通配符导入......应该避免,因为它们[混淆]读者和许多自动化工具。

[PEP 8,@ToolmakerSteve]


我真的很喜欢这个答案,但我错过了关于在 __init__.py 中没有 __all__from <package> import * 的默认行为是什么的信息,即 不导入任何模块
谢谢@Jatimir,我在没有进行实验的情况下尽可能地澄清了。我几乎想说这种情况(星号没有全部表示一个包)的行为与 就好像 __init__.py 是一个模块一样。但我不确定这是准确的,或者特别是如果排除了下划线前缀的对象。另外,我更清楚地将模块和包的部分分开。你的意见?
C
Cyker

简短的回答

__all__ 影响 from <module> import * 语句。

长答案

考虑这个例子:

foo
├── bar.py
└── __init__.py

foo/__init__.py 中:

(隐式)如果我们不定义 __all__,那么 from foo import * 只会导入 foo/__init__.py 中定义的名称。

(显式)如果我们定义 __all__ = [],那么 from foo import * 将不导入任何内容。

(显式)如果我们定义 __all__ = [ , ... ],那么 from foo import * 将只导入这些名称。

请注意,在隐式情况下,python 不会导入以 _ 开头的名称。但是,您可以使用 __all__ 强制导入此类名称。

您可以查看 Python 文档 here


C
Community

__all__ 用于记录 Python 模块的公共 API。尽管它是可选的,但应该使用 __all__

以下是 the Python language reference 的相关摘录:

模块定义的公共名称是通过检查模块命名空间中名为 __all__ 的变量来确定的;如果已定义,它必须是由该模块定义或导入的名称的字符串序列。 __all__ 中给出的名称都被认为是公开的,并且必须存在。如果 __all__ 未定义,则公共名称集包括在模块名称空间中找到的所有名称,这些名称不以下划线字符 ('_') 开头。 __all__ 应该包含整个公共 API。它旨在避免意外导出不属于 API 的项目(例如在模块中导入和使用的库模块)。

PEP 8 使用类似的措辞,但它也明确指出,当 __all__ 不存在时,导入的名称不是公共 API 的一部分:

为了更好地支持自省,模块应该使用 __all__ 属性在其公共 API 中显式声明名称。将 __all__ 设置为空列表表示该模块没有公共 API。 [...] 导入的名称应始终被视为实现细节。其他模块不得依赖对此类导入名称的间接访问,除非它们是包含模块 API 的明确记录的部分,例如 os.path 或从子模块公开功能的包的 __init__ 模块。

此外,正如其他答案中所指出的,__all__ 用于启用 wildcard importing for packages

import 语句使用以下约定:如果包的 __init__.py 代码定义了一个名为 __all__ 的列表,则当遇到 from package import * 时,它将被视为应导入的模块名称列表。


E
Eugene Yarmash

__all__ 影响 from foo import * 的工作方式。

位于模块主体内(但不在函数或类主体内)的代码可以在 from 语句中使用星号 (*):

from foo import *

* 请求将模块 foo 的所有属性(以下划线开头的属性除外)绑定为导入模块中的全局变量。当 foo 具有属性 __all__ 时,该属性的值是此类 from 语句所绑定的名称列表。

如果 foo 是一个 并且它的 __init__.py 定义了一个名为 __all__ 的列表,则它被视为遇到 from foo import * 时应导入的子模块名称的列表。如果未定义 __all__,则语句 from foo import * 导入包中定义的任何名称。这包括由 __init__.py 定义的任何名称(以及显式加载的子模块)。

请注意,__all__ 不必是列表。根据 import statement 上的文档,如果已定义,__all__ 必须是 字符串序列,这些字符串是模块定义或导入的名称。因此,您不妨使用元组来 save 一些内存和 CPU 周期。如果模块定义了一个公共名称,请不要忘记逗号:

__all__ = ('some_name',)

另请参阅Why is “import *” bad?


C
Community

这是在 PEP8 here 中定义的:

全局变量名(希望这些变量只在一个模块内使用。)约定与函数的约定大致相同。为通过 from M import * 使用而设计的模块应使用 __all__ 机制来防止导出全局变量,或使用在此类全局变量前加下划线的旧约定(您可能希望这样做以指示这些全局变量是“模块非公共”)。

PEP8 为包含主要 Python 发行版中标准库的 Python 代码提供编码约定。你越遵循这一点,你就越接近最初的意图。