首页 文章

有人可以在Python中解释__all__吗?

提问于
浏览
681

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

9 回答

  • 50

    它是该模块的公共对象列表,由 import * 解释 . 它会覆盖隐藏以下划线开头的所有内容的默认设置 .

  • 8

    链接到,但未在此明确提及,正是在使用 __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/3.5/tutorial/modules.html#importing-from-a-package

    NOTE: __all__ 仅影响 from <module> import * 行为 . __all__ 中未提及的成员仍可从模块外部访问,并可使用 from <module> import <member> 导入 .

  • 719

    我只是准确地添加这个:

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

    通常, __all__ 仅在使用 import 语句的 from xxx import * 变体时才起作用 . 这适用于包和模块 .

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

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

    最大的区别是,当您在包的 __init__.py 中省略 __all__ 的声明时,语句 from package import * 将根本不会导入任何内容(文档中说明了异常,请参阅上面的链接) .

    另一方面,如果在模块中省略 __all__ ,则"starred import"将导入模块中定义的所有名称(不以下划线开头) .

  • 5

    在Python中解释__all__?我一直看到变量__all__在不同的__init__.py文件中设置 . 这是做什么的?

    __all__做什么?

    它从模块声明了语义"public"名称 . 如果 __all__ 中有名称,则希望用户使用它,并且可以期望它不会更改 .

    它还会产生程序化影响:

    进口*

    __all__ 在一个模块中,例如 module.py

    __all__ = ['foo', 'Bar']
    

    表示当您从模块中 import * 时,仅导入 __all__ 中的那些名称:

    from module import *               # imports foo and Bar
    

    文档工具

    文档和代码自动完成工具可能(实际上应该)检查 __all__ 以确定模块中可用的名称 .

    init.py使目录成为Python包

    来自docs

    需要__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标准库中的某些包确实依赖于此,但为了这样做,它们为其导入设置了别名,例如,在_829280中:

    import os as _os, sys as _sys
    

    使用 _ 约定可以更加优雅,因为它消除了再次命名名称的冗余 . 但是它增加了导入的冗余(如果你有很多它们)并且很容易忘记这样做 - 并且你想要的最后一件事就是必须无限期地支持你想要的只是一个实现细节,只是因为在命名函数时忘了给 _ 添加前缀 .

    我个人在模块的开发生命周期的早期写了一个 __all__ ,以便其他可能使用我的代码的人知道他们应该使用什么而不是使用 .

    标准库中的大多数包也使用 __all__ .

    避免__all__有意义

    在以下情况下,坚持 _ 前缀约定代替 __all__ 是有意义的:

    • 您仍处于早期开发模式且没有用户,并且不断调整您的API .

    • 也许您确实拥有用户,但您拥有覆盖API的单元测试,并且您仍在积极地添加API并在开发中进行调整 .

    导出装饰器

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

    我从David Beazley _829289的传统进口商那里得到了这样一个出口装饰的想法 . 如果你有一个特殊的导入钩子或系统,我不保证,但如果你采用它,退出是相当简单的 - 你只需要手动将名称添加回 __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
    

    使用 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
    
  • 356

    它还改变了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__ ,以及强调内部细节,这些在使用您以前从未在现场解释器会话中使用过的东西时非常有用 .

  • 115

    来自(An Unofficial) Python Reference Wiki

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

  • 25

    __all__自定义<module> import *中的星号

    __all__自定义<package> import *中的星号


    module是要导入的 .py 文件 .

    package是具有 __init__.py 文件的目录 . 包通常包含模块 .


    """ cheese.py - an example module """
    
    __all__ = ['swiss', 'cheddar']
    
    swiss = 4.99
    cheddar = 3.99
    gouda = 10.99
    

    __all__ 让人类知道 module 的"public"特征 . [@ 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)
    

    导入模块为localname

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

    package __all____init__.py 文件中是一个包含公共模块或其他对象名称的字符串列表 . 这些功能可用于通配符导入 . 与模块一样, __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...
    
        ]
    

    应该避免使用通配符......因为他们[混淆]读者和许多自动化工具 .

    [PEP 8,@ ToolmakerSteve]

  • 84

    __all__ 用于记录Python模块的公共API . 虽然它是可选的,但应使用 __all__ .

    以下是the Python language reference的相关摘录:

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

    PEP 8使用了类似的措辞,但是当 __all__ 不存在时,它也清楚地表明导入的名称不是公共API的一部分:

    为了更好地支持内省,模块应使用__all__属性在其公共API中显式声明名称 . 将__all__设置为空列表表示该模块没有公共API . [...]应始终将导入的名称视为实施细节 . 其他模块不能依赖于对这些导入名称的间接访问,除非它们是包含模块的API的显式记录部分,例如os.path或从子模块公开功能的包的__init__模块 .

    此外,正如其他答案所指出的, __all__ 用于启用wildcard importing for packages

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

  • 143

    简答

    __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文档here .

相关问题