Python 中的 __all__ 是什么意思?

2024-11-26 08:36:00
admin
原创
253
摘要:问题描述:我__all__在__init__.py文件中看到了。它有什么作用?解决方案 1:与此相关但此处未明确提及的是何时使用。它是一个字符串列表,定义在模块上使用__all__时将导出模块中的哪些符号。from <module> import *例如,以下代码中foo.py明确导出符号bar和...

问题描述:

__all____init__.py文件中看到了。它有什么作用?


解决方案 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>

解决方案 2:

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

解决方案 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 包

来自文档:

这些__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')

为每个子包创建__init__.py声明__all__

(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']

如果你尚未准备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']

前缀_versus __all__

_默认情况下,使用 导入时,Python 将导出所有不以 开头的名称import *。如此处的 shell 会话所示,import *不会_us_non_publicus.py模块中引入名称:

$ 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__when 是有意义的:

  • 您仍处于早期开发模式,没有用户,并且正在不断调整您的 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

并且 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

解决方案 4:

我只是想补充一点:

所有其他答案均指模块。原始问题__all____init__.py文件中明确提到,因此这是关于python包的

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

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

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

最大的区别是,当你省略__all__包中的声明时__init__.py,该语句from package import *将不会导入任何内容(文档中解释了例外情况,请参阅上面的链接)。

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

解决方案 5:

它还会改变 pydoc 所显示的内容:

模块1.py

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

模块2.py

__all__ = ['a', 'b']

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

$ pydoc module1

模块 module1 的帮助:

姓名
    模块1

文件
    模块1.py

数据
    a = ‘A’
     b = ‘B’
     c = ‘C’

$ pydoc module2

模块 module2 的帮助:

姓名
    模块2

文件
    模块2.py

数据
    __all__ = ['a', 'b']
     a = 'A'
     b = 'B'

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

解决方案 6:

__all__`from <module> import `
和 中定制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文件中包含一个字符串列表,其中包含公共模块或其他对象的名称。这些功能可用于通配符导入。与模块一样,在从包中进行通配符导入时进行自定义。 [ @MartinStettner ] __all__`__all__`*

以下是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 语句明确加载的包中的任何子模块。


最后,对于各地的 Stack Overflow 答案、教授和男性解释者来说,一个受人尊敬的传统是,首先要对提出问题的人进行责备

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

[ PEP 8,@ToolmakerSteve]

解决方案 7:

简短回答

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

解决方案 8:

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

以下是Python 语言参考的相关摘录:

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

PEP 8使用了类似的措辞,尽管它也明确表示,当不存在时,导入的名称不属于公共 API 的一部分__all__

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

[...]

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

此外,正如其他答案所指出的那样,__all__用于启用包的通配符导入:

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

解决方案 9:

__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,如果定义,则__all__必须是字符串序列,这些字符串是模块定义或导入的名称。因此,您也可以使用元组来节省一些内存和 CPU 周期。如果模块定义了单个公共名称,请不要忘记逗号:

__all__ = ('some_name',)

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

解决方案 10:

PEP8 中对此进行了定义:

全局变量名称

(我们希望这些变量仅供一个模块内部使用。)约定与函数的约定大致相同。

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

PEP8 为构成 Python 主发行版中的标准库的 Python 代码提供了编码约定。你越是遵循这一点,就越接近最初的意图。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用