有人可以解释一下Python中的__all__吗?
-
09-06-2019 - |
题
我越来越多地使用Python,并且不断看到变量 __all__
设置在不同的 __init__.py
文件。有人能解释一下这是做什么的吗?
解决方案
它是该模块的公共对象的列表,解释为 import *
. 。它覆盖隐藏以下划线开头的所有内容的默认设置。
其他提示
链接到,但这里没有明确提及,正是 __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>
.
准确地说,我只是添加这个:
其他所有答案均参考 模块. 。原来的问题明确提到 __all__
在 __init__.py
文件,所以这是关于 python 的 包.
一般来说, __all__
仅当 from xxx import *
的变体 import
语句被使用。这适用于包和模块。
其他答案中解释了模块的行为。描述了包的确切行为 这里 详细。
简而言之, __all__
在包级别上所做的事情与模块大致相同,除了它处理 包内的模块 (与指定相反 模块内的名称)。所以 __all__
指定我们使用时应加载并导入到当前命名空间的所有模块 from package import *
.
最大的区别是,当你 忽略 的声明 __all__
在一个包的 __init__.py
, , 该声明 from package import *
根本不会导入任何内容(文档中解释的例外情况除外,请参阅上面的链接)。
另一方面,如果你省略 __all__
在模块中,“加星号导入”将导入模块中定义的所有名称(不以下划线开头)。
解释Python中的__all__?
我不断看到变量
__all__
设置在不同的__init__.py
文件。这是做什么的?
什么是 __all__
做?
它声明模块中语义上的“公共”名称。如果里面有一个名字 __all__
, ,用户期望使用它,并且可以期望它不会改变。
它还将产生程序化影响:
import *
__all__
在模块中,例如 module.py
:
__all__ = ['foo', 'Bar']
意味着当你 import *
从模块中,只有那些名称 __all__
进口:
from module import * # imports foo and Bar
文档工具
文档和代码自动完成工具可能(事实上,应该)也检查 __all__
确定模块中显示哪些名称可用。
__init__.py
使目录成为 Python 包
来自 文档:
这
__init__.py
需要文件才能使 Python 将目录视为包含包;这样做是为了防止具有通用名称(例如字符串)的目录无意中隐藏稍后出现在模块搜索路径上的有效模块。在最简单的情况下,
__init__.py
可以只是一个空文件,但它也可以执行包的初始化代码或设置__all__
多变的。
所以 __init__.py
可以声明 __all__
为一个 包裹.
管理 API:
一个包通常由可以相互导入的模块组成,但它们必须通过一个模块捆绑在一起 __init__.py
文件。该文件使该目录成为真正的 Python 包。例如,假设您有以下内容:
package/
|-__init__.py # makes directory a Python package
|-module_1.py
|-module_2.py
在里面 __init__.py
你写:
from module_1 import *
from module_2 import *
并在 module_1
你有:
__all__ = ['foo',]
并在 module_2
你有:
__all__ = ['Bar',]
现在您已经提供了一个完整的 api,其他人可以在导入您的包时使用,如下所示:
import package
package.foo()
package.Bar()
而且他们不会拥有您在创建模块时使用的所有其他名称,从而使模块变得混乱 package
命名空间。
__all__
在 __init__.py
经过更多工作后,也许您认为模块太大并且需要拆分。所以你执行以下操作:
package/
|-__init__.py
|-module_1/
| |-__init__.py
| |-foo_implementation.py
|-module_2/
|-__init__.py
|-Bar_implementation.py
并且在每个 __init__.py
你声明一个 __all__
, ,例如在模块_1中:
from foo_implementation import *
__all__ = ['foo']
和 module_2 的 __init__.py
:
from Bar_implementation import *
__all__ = ['Bar']
您可以轻松地将内容添加到您的 API,您可以在子包级别而不是子包的模块级别进行管理。如果您想向 API 添加新名称,只需更新 __init__.py
, ,例如在模块_2中:
from Bar_implementation import *
from Baz_implementation import *
__all__ = ['Bar', 'Baz']
如果您还没有准备好发布 Baz
在顶级 API 中,在您的顶级中 __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 将导出所有不以 _
. 。你当然 可以 依靠这个机制。Python标准库中的一些包,实际上, 做 依赖于此,但为此,他们为导入别名,例如, ctypes/__init__.py
:
import os as _os, sys as _sys
使用 _
约定可以更加优雅,因为它消除了再次命名的冗余。但它增加了进口的冗余(如果你有很多进口的话) 简单的 忘记始终如一地执行此操作 - 您想要的最后一件事就是必须无限期地支持您只想作为实现细节的东西,只是因为您忘记了前缀 _
命名函数时。
我个人写一个 __all__
在我的模块开发生命周期的早期,以便其他可能使用我的代码的人知道他们应该使用什么和不应该使用什么。
标准库中的大多数包也使用 __all__
.
回避时 __all__
说得通
坚持下去是有意义的 _
前缀约定代替 __all__
什么时候:
- 您仍处于早期开发模式并且没有用户,并且正在不断调整您的 API。
- 也许您确实有用户,但您有涵盖 API 的单元测试,并且您仍在积极添加 API 并在开发中进行调整。
一个 export
装饰者
使用的缺点 __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
以及 API 配置 import *
也会起作用:
$ 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
它还更改了 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: 姓名 module1 文件 module1.py 数据 A = 'A' 乙 = 'B' C = 'C'
$ pydoc模块2
Help on module module2: 姓名 module2 文件 module2.py 数据 __全部__ = ['a', 'b'] A = 'A' 乙 = 'B'
我声明 __all__
在我的所有模块中,以及强调内部细节,这些在实时口译会话中使用您以前从未使用过的东西时确实很有帮助。
模块定义的公共名称是通过检查模块的命名空间中名为 的变量来确定的
__all__
;如果定义,它必须是由该模块定义或导入的名称的字符串序列。给出的名字在__all__
都被认为是公开的并且必须存在。如果__all__
未定义,公共名称集包括在模块名称空间中找到的所有不以下划线字符(“_”)开头的名称。__all__
应包含整个公共 API。其目的是避免意外导出不属于 API 的项目(例如在模块中导入和使用的库模块)。
__all__
定制 星号 在 from <module> import *
__all__
定制 星号 在 from <package> import *
A 模块 是一个 .py
需要导入的文件。
A 包裹 是一个目录 __init__.py
文件。包通常包含模块。
模块
""" cheese.py - an example module """
__all__ = ['swiss', 'cheddar']
swiss = 4.99
cheddar = 3.99
gouda = 10.99
__all__
让人类了解某个事物的“公共”特征 模块.[@亚伦霍尔] 此外,pydoc 也能识别它们。[@隆波克]
从 模块 进口 *
怎么看 swiss
和 cheddar
被带入本地命名空间,但不是 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)
套餐
在里面 __init__.py
的文件 包裹 __all__
是包含公共模块或其他对象名称的字符串列表。这些功能可用于通配符导入。与模块一样, __all__
定制 *
从包中通配符导入时。[@MartinStettner]
这是摘录自 Python MySQL 连接器 __init__.py
:
__all__ = [
'MySQLConnection', 'Connect', 'custom_error_exception',
# Some useful constants
'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
'HAVE_CEXT',
# Error handling
'Error', 'Warning',
...etc...
]
默认情况下, 星号没有 __all__
对于一个包, ,很复杂,因为明显的行为会很昂贵:使用文件系统搜索包中的所有模块。相反,在我阅读文档时,只有中定义的对象 __init__.py
进口:
如果
__all__
未定义,该语句from sound.effects import *
做 不是 从包中导入所有子模块sound.effects
进入当前命名空间;它只确保包sound.effects
已导入(可能运行任何初始化代码__init__.py
),然后导入包中定义的任何名称。这包括由以下定义的任何名称(以及显式加载的子模块)__init__.py
. 。它还包括由先前的 import 语句显式加载的包的任何子模块。
通配符导入...应避免,因为它们会[迷惑]读者和许多自动化工具。
[PEP 8, ,@ToolmakerSteve]
简短回答
__all__
影响 from <module> import *
声明。
长答案
考虑这个例子:
foo
├── bar.py
└── __init__.py
在 foo/__init__.py
:
(隐式)如果我们不定义
__all__
, , 然后from foo import *
只会导入定义在中的名称foo/__init__.py
.(显式)如果我们定义
__all__ = []
, , 然后from foo import *
不会导入任何内容。(显式)如果我们定义
__all__ = [ <name1>, ... ]
, , 然后from foo import *
只会导入这些名称。
请注意,在隐式情况下,python 不会导入以 _
. 。但是,您可以使用强制导入此类名称 __all__
.
可以查看Python文档 这里.
__all__
用于记录 Python 模块的公共 API。虽然它是可选的, __all__
应该使用。
这是相关摘录 Python 语言参考:
模块定义的公共名称是通过检查模块的命名空间中名为的变量来确定的
__all__
;如果定义,它必须是由该模块定义或导入的名称的字符串序列。给出的名字在__all__
都被认为是公开的并且必须存在。如果__all__
未定义,公共名称集包括在模块的命名空间中找到的所有不以下划线字符(“_”)开头的名称。__all__
应包含整个公共 API。其目的是避免意外导出不属于 API 的项目(例如在模块中导入和使用的库模块)。
PEP 8 使用类似的措辞,尽管它也明确表示导入的名称不是公共 API 的一部分 __all__
缺席:
为了更好地支持自省,模块应该使用以下方式在其公共 API 中显式声明名称:
__all__
属性。环境__all__
为空列表表示该模块没有公共 API。[...]
导入的名称应始终被视为实现细节。其他模块不得依赖于对此类导入名称的间接访问,除非它们是包含模块的 API 的显式记录部分,例如
os.path
或一个包的__init__
公开子模块功能的模块。
此外,正如其他答案中指出的, __all__
用于启用 包的通配符导入:
import 语句使用以下约定:如果一个包的
__init__.py
代码定义了一个名为的列表__all__
, ,它被认为是应该导入的模块名称列表from package import *
遇到。