我在__init__.py文件中看到__all__。它做什么?


当前回答

它还改变了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:

NAME
    module1

FILE
    module1.py

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

$pydoc模块2

Help on module module2:

NAME
    module2

FILE
    module2.py

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

我在我的所有模块中声明__all__,并强调内部细节,这些在使用以前从未在实时解释器会话中使用过的东西时非常有用。

其他回答

__all__从<模块>导入中自定义**和来自<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]

从模块导入*

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

将模块导入为localname

>>> import cheese as ch
>>> ch.swiss, ch.cheddar, ch.gouda
(4.99, 3.99, 10.99)

包装

在包__all__的__init__.py文件中,有一个字符串列表,其中包含公共模块或其他对象的名称。这些功能可用于通配符导入。与模块一样,__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__,sound.effects import*中的语句不会将sound.eeffects包中的所有子模块导入当前命名空间;它只确保包sound.effects已经导入(可能在__init__.py中运行任何初始化代码),然后导入包中定义的任何名称。这包括__init__.py定义的任何名称(以及显式加载的子模块)。它还包括以前的import语句显式加载包的任何子模块。


最后,随处可见的堆积如山的答案、教授和漫画家,这是一个受人尊敬的传统,这是对一开始就提出问题的指责:

通配符导入。。。应该避免,因为它们会混淆读者和许多自动化工具。

[PEP 8,@ToolmakerSteve]

__all__影响from foo import*的工作方式。

模块主体(但不在函数或类主体中)内的代码可以在from语句中使用星号(*):

from foo import *

*请求将foo模块的所有属性(以下划线开头的属性除外)绑定为导入模块中的全局变量。当foo有一个__all__属性时,该属性的值是由该类型的from语句绑定的名称列表。

如果foo是一个包,并且它的__init__.py定义了一个名为__all__的列表,那么当遇到from foo import*时,它将被视为应该导入的子模块名称列表。如果__all__未定义,foo import*的语句将导入包中定义的任何名称。这包括__init__.py定义的任何名称(以及显式加载的子模块)。

注意__all__不一定是列表。根据import语句的文档,如果已定义,__all__必须是由模块定义或导入的字符串序列。因此,您不妨使用元组来节省一些内存和CPU周期。如果模块定义了一个公共名称,请不要忘记逗号:

__all__ = ('some_name',)

另请参见为什么“import*”不好?

用Python解释所有内容?我一直看到变量__all__设置在不同的__init__.py文件中。这有什么作用?

__all__做什么?

它从模块中声明语义上的“公共”名称。如果__all__中有一个名称,用户应该使用它,并且他们可以期望它不会更改。

它还将产生方案效果:

进口*

__模块中的所有__,例如module.py:

__all__ = ['foo', 'Bar']

表示从模块导入*时,只导入__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
├── module_1.py
└── module_2.py

让我们用Python创建这些文件,这样您就可以继续操作了-您可以将以下内容粘贴到Python 3 shell中:

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

而且,在创建模块时,该包不会包含您使用的所有其他实现细节,这些模块会使包名称空间变得混乱。

__所有__在__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')

为每个声明__all__的子包创建__init__.pys:

(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__:

默认情况下,当使用import*导入时,Python将导出所有不以_开头的名称。如shell会话所示,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并在开发中进行调整。

导出装饰器

使用__all__的缺点是,必须将导出的函数和类的名称写两次,并且信息与定义分开。我们可以使用装饰器来解决这个问题。

我从大卫·比兹利(David Beazley)关于包装的演讲中得到了这样一个出口装饰师的想法。这种实现在CPython的传统导入程序中似乎运行良好。如果您有一个特殊的导入挂钩或系统,我不能保证它,但如果您采用它,那么退出是相当简单的-您只需要手动将名称添加回__all__

因此,例如,在实用程序库中,您可以定义decorator:

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__影响<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文档。

我只是准确地补充一下:

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

通常,__all__仅在使用import语句的from xxx import*变体时生效。这适用于软件包和模块。

其他答案中解释了模块的行为。这里详细描述了包的确切行为。

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

最大的区别是,当您在包的__init__.py中省略__all__的声明时,包import*中的语句根本不会导入任何内容(除了文档中解释的例外,请参见上面的链接)。

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