有人可以用Python解释__all__吗?

一曲冷凌霜 提交于 2019-12-16 20:09:16

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

我越来越多地使用Python,并且不断看到在不同的__init__.py文件中设置了变量__all__ 。 有人可以解释这是什么吗?


#1楼

我只是添加这是为了精确:

所有其他答案均涉及模块 。 最初的问题在__init__.py文件中明确提到__all__ ,所以这是关于python 包的

通常, __all__仅在使用import语句的from xxx import *变体时才起作用。 这适用于软件包以及模块。

模块的行为在其他答案中进行了说明。 包的确切行为在此处详细描述。

简而言之,包级别的__all__模块具有几乎相同的功能,除了它处理包中的模块 (与在模块中指定名称相反)。 因此__all__指定当我们from package import *使用时应加载并导入到当前名称空间的所有模块。

最大的区别是,当您省略软件包的__init__.py__all__声明时from package import *的语句将根本不会导入任何内容(文档中有例外说明,请参见上面的链接)。

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


#2楼

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

module1.py

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

module2.py

__all__ = ['a', 'b']

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

$ pydoc module1

Help on module module1:

NAME
    module1

FILE
    module1.py

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

$ pydoc module2

Help on module module2:

NAME
    module2

FILE
    module2.py

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

我在所有模块中都声明了__all__ ,并强调了内部细节,当使用在实时解释器会话中从未使用过的功能时,这些确实有用。


#3楼

用Python解释__all__吗?

我一直看到在不同的__init__.py文件中设置了变量__all__

这是做什么的?

__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名称空间混乱的模块时使用的所有其他名称。

__init__.py __all__

经过更多的工作,也许您已经确定模块太大,需要拆分。 因此,您需要执行以下操作:

 package/
   |-__init__.py
   |-module_1/
   |  |-__init__.py
   |  |-foo_implementation.py
   |-module_2/
      |-__init__.py
      |-Bar_implementation.py

然后在每个__init__.py声明一个__all__ ,例如在module_1中:

from foo_implementation import *
__all__ = ['foo']

和module_2的__init__.py

from Bar_implementation import *
__all__ = ['Bar']

而且,您可以轻松地将内容添加到您可以在子包级别而不是子包的模块级别管理的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将导出所有不以_开头的名称。 您当然可以依靠这种机制。 实际上,Python标准库中的某些软件包确实依赖于此,但是要这样做,它们会为自己的导入ctypes/__init__.py别名,例如在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()

无论是作为主程序运行还是由其他函数导入,此方法都可以正常工作。

$ 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

#4楼

__all__自定义* from <module> import *

__all__自定义* from <package> import *


模块是要导入的.py文件。

软件包是带有__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)

包数

__init__.py文件中, __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...

    ]

缺省情况下, 星号不带__all__的软件包非常复杂,因为明显的行为是很昂贵的:使用文件系统搜索软件包中的所有模块。 相反,在阅读文档时,仅导入__init__.py中定义的对象:

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


应避免使用通配符导入,因为它们会使读者和许多自动化工具感到困惑。

[ PEP 8 ,@ ToolmakerSteve]


#5楼

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

这是Python语言参考中的相关摘录:

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

PEP 8使用了类似的措辞,尽管它也清楚地表明,当缺少__all__时,导入的名称不属于公共API:

为了更好地支持自省,模块应使用__all__属性在其公共API中显式声明名称。 将__all__设置为空列表表示该模块没有公共API。

[...]

导入的名称应始终被视为实现细节。 除非其他模块是包含模块的API中明确记录的一部分,否则其他模块不得依赖对此类导入名称的间接访问,例如os.path或从子模块公开功能的软件包的__init__模块。

此外,如其他答案所指出的, __all__用于启用软件包的通配符导入

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!