一、定义:模块公开接口的一种约定

__all__可以在模块级别暴露接口,形式如下:
all:List[AnyStr] = [“foo”, “bar”] # 默认是一个list类型
备注:'foo’可以是方法名,变量名,模块名

Python 没有原生的可见性控制,其可见性的维护是靠一套需要大家自觉遵守的”约定“,比如,下划线开头的变量对外部不可见。
(即定义模块属性(变量或者方法或者类)的时候,例如变量 _a, 默认是不能在其他模块内部导入的,除非添加到特殊变量__all__里面,这种命名规则是属于python的弱封装机制,特殊变量__all__属于另外一种白名单封装的机制)

all 是针对模块公开接口的一种约定,以提供了”白名单“的形式暴露接口。如果定义了__all__,其他文件中使用from xxx import *
导入该文件时,只会导入 all 列出的成员(包括弱封装机制的成员),可以其他成员都被排除在外。

# 如,test1.py,test2.py,test3.py三个文件:
# -------------test1.py---------------
def func():
    pass
def _func():
    pass
    
# -------------test2.py---------------
import test1
 
__all__ = ['func2', 'test1', "_func2"] # 定义特殊变量__all__
def func2():
    pass

def func22():
    pass
    
def _func2():
    pass
    
# ------------test3.py----------------
from test2 import *

#能正常引用
func2() 
_func2()
test1.func()

#不能正常引用
func22() # 原因:test2.py的__all__里面没有包含'func22'
test1._func() # 原因:默认下划线开头的变量,方法对外部不可引用(除非在test1.py的定义特殊变量__all__,并将带下划线的变量或者方法名已str的形式添加到__all__里面)

二、控制 from xxx import * 的行为的意义

  • python不提倡用 from xxx import * 这种写法。如果一个模块 xxx 没有定义 all,执行 from spam import * 时会将 xxx 中所有非下划线开头的成员(包括该模块import的其他模块成员)都会导入当前命名空间,这样就可能弄脏当前的命名空间。
  • 显式声明了 all,import * 就只会导入 all 列出的成员,如果 all 定义有误,还会明确地抛出异常,方便检查错误。

当使用__all__的时候,所有非下划线开头的成员(包括该模块import的其他模块成员)都可以添加到特殊变量__all__,这样就能被其他模块引用

三、常用方式:在 init.py 中暴露整个包的 API

编写库时,经常会在 init.py 中暴露整个包的 API,而这些 API 的实现可能是在包的其他模块中。如果仅仅这样写:from xxx import a, b,一些代码检查工具,如 pyflakes 会报错,认为变量 a和 b import 了但没被使用。一个可行的方法是把这个警告压掉:from xxx import a, b # noqa (No Q/A,即无质量保证),但更好的方法是显式定义 all,这样代码检查工具就会理解,从而不再报 unused variables 的警告。

(通俗的来说就是写包的时候,我们会在包的__init__.py中导入该包下的其他模块,即暴露接口,这样其他包导入的时候就可以直接使用from my_package import * 导入整个包的API,同时可以通过 from my_package import function1, Class1 或 from my_package import function2, Class2 来单独导入指定属性。)

  • module1.py 包含以下内容:
def function1():
    print("This is function1 in module1")

class Class1:
    def __init__(self):
        print("This is Class1 constructor in module1")
  • module2.py 包含以下内容:
def function2():
    print("This is function2 in module2")

class Class2:
    def __init__(self):
        print("This is Class2 constructor in module2")
  • init.py 包含以下内容:
from .module1 import function1, Class1
from .module2 import function2, Class2

__all__ = ['function1', 'Class1', 'function2', 'Class2']

四、定义 all 需要注意的地方

  • all 的形式都是 list类型。如果写成其他类型, pyflakes 等 lint 工具可能无法识别。
  • 不能动态生成 all,如使用列表解析式(列表推导式)。all 的作用是定义公开接口,需要以字面量的形式显式写出来。
  • 即使定义了 all, 也不应该在非临时代码中使用 from xxx import * 语法,模块就是命名空间隔离的执行者。如果打破了这一层,引入诸多动态因素,生产环境中跑的代码就可能充满不确定性,调试也会变得困难。
import importlib

module_name = 'math'  # 要动态导入的模块名
func_name = 'sqrt'  # 要调用的函数名

# 动态导入模块
module = importlib.import_module(module_name)

# 调用模块中的函数
result = getattr(module, func_name)(4)

print(result)  # 输出 2.0,即对4进行开方操作

在这个例子中,我们使用importlib模块动态地导入模块"math",然后通过getattr()函数来获取并调用该模块中的函数"sqrt",对4进行开方操作并输出结果。这种动态模拟导入的方式可以让我们在运行时根据需要加载并调用不同的模块和函数,从而增加代码的灵活性。
  • 按照 PEP8 建议的风格,all 应该写在所有 import 语句下面,函数、常量等成员定义的上面。 如果一个模块需要暴露的接口改动频繁,all 可以这样定义:
all = [
 “foo”,
 “bar”,
 “egg”,
 ]

  • 这样修改一个暴露的接口只修改一行,方便版本控制的时候看 diff。最后多出的逗号在 Python 中是允许的,符合 PEP8 风格。

学习资料来源:https://zhuanlan.zhihu.com/p/54274339