Python 中的__all__

Hiten Kanwar 2021年11月30日
Python 中的__all__

随着我们深入了解包和模块,我们可能会遇到设置在不同 _init_.py 文件中的变量 __all__

__init__.py 文件是使 Python 将目录视为某些包含包的文件。此文件可防止具有相似名称(例如字符串)的目录隐藏稍后可能出现在模块搜索路径上的有效模块。

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

因此,__init__.py 可以为包声明 __all__ 变量。

该模块的公共对象列表在 __all__ 变量中给出。它由 import *解析。此变量覆盖了隐藏给定命名空间中以下划线开头的所有内容的默认设置。

例如,

__all__ = ["a", "b"]
c = 5
a = 10


def b():
    return "b"

现在我们在下面的代码中导入它。

from sample import *

print(a)  # will work fine
print(b)  # will work fine
print(c)  # will generate an error

在上面的例子中,我们使用 import * 将所有公共对象从文件 sample.py 导入到这个文件中。这意味着该文件将导入并支持文件 sample.py 的所有公共对象。

对象 ab 将被导入,新代码将在使用这些对象的地方完美运行。问题出现在使用第三个对象 c 时。该对象永远不会导入到新文件中,因为它不是公共对象,也不是 __all__ 变量的一部分。所以这部分代码会产生错误。

有一个替代方案。默认情况下,Python 将负责导出所有不以下划线 _ 开头的名称。人们当然可以依赖这种机制。在 Python 标准库中,一些包确实依赖于此,但为此,它们将它们的导入别名化,例如,os_ossys_sys 等。

使用 _ 约定更方便,因为它消除了重复命名名称的冗余。但它确实为导入添加了冗余(如果你有很多),并且更容易忘记始终如一地执行此操作。

标准库中的很多包都使用 __all__。当仍处于早期开发模式并且没有用户并且不断调整你的 API 时,使用 _ 前缀约定代替 __all__ 是有意义的。也许有一些用户,但有一个覆盖 API 的单元测试,并且仍在积极更新和添加 API,并在开发中进行调整。