Python中的__all__是什么意思?

1702 投票
10 回答
635288 浏览
提问于 2025-04-11 09:20

我在 __init__.py 文件里看到 __all__,这是什么东西?

10 个回答

603

解释一下Python中的__all__是什么?

我在不同的__init__.py文件中看到变量__all__被设置。

这是什么意思?

__all__的作用是什么?

它用来声明模块中“公开”的名字。如果某个名字在__all__里,用户就可以放心使用这个名字,并且可以期待它不会改变。

它还有一些程序上的影响:

import *

在一个模块中,比如module.py

__all__ = ['foo', 'Bar']

这意味着当你从这个模块中import *时,只有__all__里的名字会被导入:

from module import *               # imports foo and Bar

文档工具

文档和代码自动补全工具可能会检查__all__,以确定哪些名字可以从模块中使用。

__init__.py让一个目录成为Python包

根据文档

需要__init__.py文件来让Python把目录视为包含包;这样做是为了防止同名目录(比如string)意外隐藏在模块搜索路径后面的有效模块。

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

所以__init__.py可以为一个声明__all__

管理API:

一个包通常由多个模块组成,这些模块可能会相互导入,但它们必须通过__init__.py文件联系在一起。这个文件使得目录成为真正的Python包。例如,假设你在一个包里有以下文件:

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

让我们用Python创建这些文件,这样你可以跟着做 - 你可以把以下内容粘贴到Python 3的命令行中:

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命名空间的混乱。

__all____init__.py中的使用

经过更多的工作,可能你决定模块太大(比如有几千行?)需要拆分。所以你可以这样做:

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')

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

(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__的区别:

默认情况下,Python会导出所有不以_开头的名字,当使用import *时。正如这里的命令行会话所示,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。

一个export装饰器

使用__all__的缺点是你必须将要导出的函数和类的名字写两遍 - 而且这些信息与定义是分开的。我们可以使用一个装饰器来解决这个问题。

我从David Beazley关于打包的演讲中得到了这个export装饰器的想法。这种实现似乎在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()

无论是作为主程序运行还是被其他函数导入,这都能正常工作。

$ 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
1563

这里提到的一个重要概念是__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>的方式导入。

856

这是一个该模块的公共对象列表,使用 import * 的方式来理解。它会覆盖默认的规则,即隐藏所有以下划线开头的内容。

撰写回答