Python中的__all__是什么意思?
我在 __init__.py
文件里看到 __all__
,这是什么东西?
10 个回答
解释一下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
这里提到的一个重要概念是__all__
的使用时机。它是一个字符串列表,用来定义在使用from <module> import *
时,模块中哪些符号会被导出。
比如,在一个名为foo.py
的文件中,下面的代码明确导出了bar
和baz
这两个符号:
__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>
的方式导入。
这是一个该模块的公共对象列表,使用 import *
的方式来理解。它会覆盖默认的规则,即隐藏所有以下划线开头的内容。