【问题标题】:Can someone explain __all__ in Python?有人可以用 Python 解释 __all__ 吗?
【发布时间】:2018-09-05 02:13:53
【问题描述】:

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

【问题讨论】:

    标签: python syntax namespaces


    【解决方案1】:

    __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/tutorial/modules.html#importing-from-a-package

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

    【讨论】:

    • 我们不应该将 baz 打印为print(baz())吗?
    • @JohnCole baz 是函数对象,baz() 将运行函数对象
    • 目的是为了说明符号是导出的。是否执行功能是次要的。
    • 我觉得令人费解的是,直到今天,还没有办法通过直接引用函数/对象来填充 __all__。相反,我们必须在名称更改时输入他们的名字并单独更正它们。对于活动代码库来说似乎很容易出错。
    • @JulioCezarSilva 有点离题,但值得注意的是,对于类和函数,您可以使用 __name__ 属性
    【解决方案2】:

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

    【讨论】:

    • 以下划线开头的对象,或者如果__all__ 存在,则在__all__ 中未提及的对象,并未完全隐藏;如果您知道它们的名字,就可以正常查看和访问它们。只有在“import *”的情况下,无论如何都不推荐,这种区别才有意义。
    • @BrandonRhodes:这也不完全正确:建议只导入您知道为import * 设计的模块(例如tk)。如果是这种情况,一个很好的提示是存在__all__ 或模块代码中以下划线开头的名称。
    • 公共和内部接口 - python.org/dev/peps/pep-0008/#id50,为了更好地支持自省,模块应该使用 __all__ 属性在其公共 API 中显式声明名称。将 __all__ 设置为空列表表示该模块没有公共 API。
    • 我不确定如果tk 在今天发布(或者甚至在 2012 年发布),推荐的做法是使用 from tk import *。我认为这种做法被接受是因为惯性,而不是有意的设计。
    • 总结一下:如果你有__all__import *将导入__all__中的所有内容,否则,它将导入不以下划线开头的所有内容。
    【解决方案3】:

    用 Python 解释全部

    我不断看到变量__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 包

    来自docs

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

    并且该包不会包含您在创建模块时使用的所有其他实现细节,这些模块会弄乱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')
    

    为每个声明__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__

    默认情况下,Python 将导出所有不以_ 开头的名称。您当然可以依赖这种机制。 Python 标准库中的一些包,其实确实依赖这个,但是为了这样做,他们为自己的imports加上了别名,例如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
    

    【讨论】:

    • 交叉引用:我在this CW answer 中提到了你的装饰器,关于如何编写@export 装饰器的问题。
    • 在帮助一个相对较新的 python 开发人员了解使用__init__.py 导入模块/包的过程和__all__ 的使用方面,这是我所见过的最有用的答案跨度>
    • 这对我帮助很大。我的问题是,我要导入的子模块都是生成的文件,它们的符号中有很多我想去掉的杂物,而不必手动确保__all__ 是正确的。
    • @MikeC 那么也许你也应该生成你的__all__ - 但我会说你有一个不稳定的API......这将是一些全面的验收测试。
    • @AaronHall "他们不会有所有其他的名字......把包命名空间弄得乱七八糟" 但是他们有名字module_1module_2;在__init__.py 中包含明确的del module_1 是否可以?我认为这是值得的吗?
    【解决方案4】:

    为了准确起见,我只是添加了这个:

    所有其他答案参考模块。原始问题在__init__.py 文件中明确提到了__all__,所以这是关于python packages

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

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

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

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

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

    【讨论】:

    • from package import * 仍将导入在__init__.py 中定义的所有内容,即使没有all。重要的区别是没有__all__,它不会自动导入包目录中定义的任何模块。
    • all 包含 [foo, bar] 并且在 test.py 文件中如果我们使用:from package import *,那么,在本地命名空间中导入 foo 和 bar test.py 还是在 foo 和 bar 自己的命名空间中?
    【解决方案5】:

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

    module1.py

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

    module2.py

    __all__ = ['a', 'b']
    
    a = "A"
    b = "B"
    c = "C"
    

    $ pydoc 模块1

    关于模块 module1 的帮助: 姓名 模块1 文件 模块1.py 数据 a = 'A' b = 'B' c = 'C'

    $ pydoc 模块2

    关于模块 module2 的帮助: 姓名 模块2 文件 模块2.py 数据 __all__ = ['a', 'b'] a = 'A' b = 'B'

    我在我的所有模块中声明了__all__,并在内部细节下划线,这对于在现场口译会话中使用以前从未使用过的东西非常有帮助。

    【讨论】:

      【解决方案6】:

      __all__from &lt;module&gt; import * 中自定义*from &lt;package&gt; import *


      module 是要导入的 .py 文件。

      package 是一个包含__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 module as localname

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

      包裹

      __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...
      
          ]
      

      默认情况asterisk with no __all__ for a package 很复杂,因为显而易见的行为代价高昂:使用文件系统搜索包中的所有模块。相反,在我阅读文档时,只导入了 __init__.py 中定义的对象:

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


      最后,一个受人尊敬的堆栈溢出答案、教授和随处可见的人的传统,是首先提出问题的bon mot

      通配符导入......应该避免,因为它们[混淆]阅读器和许多自动化工具。

      [PEP 8, @ToolmakerSteve]

      【讨论】:

      • 我真的很喜欢这个答案,但我错过了关于from &lt;package&gt; import * 没有__all____init__.py 中的默认行为的信息,即不导入任何模块.
      • 谢谢@Jatimir,我在不进行实验的情况下尽可能地澄清了。我几乎想说这种情况(没有全部用于包的星号)的行为与就像__init__.py 是一个模块一样。但我不确定这是准确的,或者特别是如果排除了下划线前缀的对象。另外,我更清楚地将模块和包的部分分开。你的想法?
      【解决方案7】:

      来自(An Unofficial) Python Reference Wiki

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

      【讨论】:

      【解决方案8】:

      简答

      __all__ 影响from &lt;module&gt; import * 语句。

      长答案

      考虑这个例子:

      foo
      ├── bar.py
      └── __init__.py
      

      foo/__init__.py:

      • (隐式)如果我们不定义__all__,那么from foo import * 将只导入foo/__init__.py 中定义的名称。

      • (显式)如果我们定义__all__ = [],那么from foo import * 将不会导入任何内容。

      • (显式)如果我们定义__all__ = [ &lt;name1&gt;, ... ],那么from foo import * 将只导入这些名称。

      请注意,在隐式情况下,python 不会导入以_ 开头的名称。但是,您可以使用 __all__ 强制导入此类名称。

      您可以查看Python文档here

      【讨论】:

        【解决方案9】:

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

        这是来自the Python language reference的相关摘录:

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

        PEP 8 使用类似的措辞,尽管它也明确指出,当__all__ 不存在时,导入的名称不是公共 API 的一部分:

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

        [...]

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

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

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

        【讨论】:

          【解决方案10】:

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

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

          from foo import *
          

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

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

          请注意,__all__ 不必是列表。根据import statement 上的文档,如果已定义,__all__ 必须是 字符串序列,这些字符串是由模块定义或导入的名称。所以你也可以使用一个元组来save一些内存和CPU周期。如果模块定义了一个公共名称,请不要忘记逗号:

          __all__ = ('some_name',)
          

          另见Why is “import *” bad?

          【讨论】:

            【解决方案11】:

            这是在 PEP8 here 中定义的:

            全局变量名

            (希望这些变量只在一个模块中使用。)约定与函数的约定大致相同。

            为通过from M import * 使用而设计的模块应使用__all__ 机制来防止导出全局变量,或使用在此类全局变量前加下划线的旧约定(您可能希望这样做以表明这些全局变量是“模块非公开”)。

            PEP8 为包含主要 Python 发行版中标准库的 Python 代码提供编码约定。你越是遵循这个,你就越接近最初的意图。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-05-29
              • 2010-12-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-12-03
              • 1970-01-01
              相关资源
              最近更新 更多