python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python __all__用法

Python中__all__用法及常见误区详解

作者:MediaTea

__all__属性是Python中用来控制模块导入行为的关键工具,它可以帮助你保持代码的整洁和封装性,防止意外地暴露内部实现细节,这篇文章主要介绍了Python中__all__用法及常见误区的相关资料,需要的朋友可以参考下

前言

在 Python 的模块与包体系中,__all__ 是一个看似简单却极具控制力的变量。它用于定义一个模块或包在使用 from xxx import * 时的公开接口(Public API),是实现封装、接口管理与工程化代码结构的关键工具。

Python 的导入机制向来强调“显式优于隐式”,而 __all__ 正体现了这一哲学:作者显式决定用户能看到什么、不能看到什么。

一、__all__ 的作用概述

在任何模块或者包的 __init__.py 中,只要定义了:

__all__ = ["name1", "name2", ...]

那么当用户执行:

from module import *

只有 __all__ 中列出的名称会被导入。

它本质上是一个导出白名单。

如果模块或包没有定义 __all__,那么:

from module import *

将导入所有不以下划线 _ 开头的名称。

示例:

# module.pyx = 1_y = 2def func(): pass

执行:

from module import *print(dir())

结果包含:

xfunc

而 _y 被视为“内部名称”,不会被导入。

这也是为什么企业级项目往往认为 from ... import * 不够安全:

没定义 __all__ 时,公开接口是隐式推断的,容易产生污染与冲突。

二、__all__ 的标准写法与行为

(1)基本示例

# mathutils.py__all__ = ["add", "mul"]
def add(a, b):    return a + b
def mul(a, b):    return a * b
def _secret():    return "hidden"

用户端:

from mathutils import *
print(add(1, 2))print(mul(2, 3))_secret()   # 报错:未导入

_secret 即使不以下划线开头,也不会被导入。

(2)在包的 __init__.py 中使用 __all__

位置结构:

mypkg/    __init__.py    a.py    b.py

__init__.py:

from .a import func_afrom .b import func_b
__all__ = ["func_a", "func_b"]

用户只需:

from mypkg import *

即可获得统一后的公共接口。

(3)在多级包中的控制

层级结构:

mypkg/    __init__.py    utils/        __init__.py        io.py

两层都可使用 __all__ 各自管理对上层的可见性,最终决定用户能访问什么。

三、__all__ 的设计动机

企业级项目通常强调 API 的稳定性与可控性,而 __all__ 能解决三个工程痛点。

(1)防止命名污染(避免 * 导入把内部逻辑暴露出去)

没有 __all__ 时,一个模块中新增任何“非下划线名称”,都会被自动公开,导致 API 不稳定。

(2)构建“面向用户的公共接口”

特别是在复杂包的 __init__.py 中,可以用 __all__ 构建统一接口层,让用户与内部结构解耦。

(3)稳固的封装机制

封装不仅是“不让别人看到”,更重要是“不让别人依赖内部结构”。

__all__ 让维护者可以明确声明 “这是对外 API,其余均为内部实现”。

四、常见使用模式

(1)显式导出函数与类

__all__ = ["A", "B", "utility"]
class A: ...class B: ...def utility(): ...

(2)阻止特定名称被导入

即使不以下划线开头,也可阻止导出:

def internal_logic():    ...
__all__ = ["api"]

(3)组合多个子模块的接口成为统一 API

# __init__.pyfrom .math import add, subfrom .string import format_name
__all__ = ["add", "sub", "format_name"]

这让包即使内部结构复杂,对外接口仍保持整洁一致。

(4)动态构建 __all__

有时模块想自动暴露所有非下划线名称,但仍显式控制:

__all__ = [name for name in globals() if not name.startswith("_")]

或按规则过滤:

__all__ = [name for name in globals() if callable(globals()[name])]

适用于自动化驱动的大型工具包。

五、常见误区澄清

误区 1:__all__ 会影响普通 import

例如:

import mathutilsmathutils.func()  # 无论 __all__ 是否包含 func,都可访问

说明:

__all__ 仅影响 from module import *,不影响普通 import。

误区 2:定义了 __all__ 下划线函数就一定隐藏

错。

__all__ = ["_internal"]

from module import * 仍会导入 _internal,因为作者将其显式加入。

误区 3:在包中使用 __all__ 会自动导入模块

不会。

# __init__.py__all__ = ["a", "b"]

不会自动导入子模块 a.py 或 b.py,需要你显式写:

from . import a, b

六、完整示例(构建可维护的公共接口)

目录:

mypkg/    __init__.py    add.py    multiply.py

add.py:

def add(a, b):    return a + b

multiply.py:

def mul(a, b):    return a * b

__init__.py:

from .add import addfrom .multiply import mul
__all__ = ["add", "mul"]
print("mypkg loaded.")

用户端:

from mypkg import *
print(add(1, 2))print(mul(2, 5))

小结

__all__ 是 Python 模块化体系中极具工程价值的接口控制工具。它通过显式定义“允许被星号导入的名称”,使开发者能够严格管理对外 API,避免命名污染,并让大型包的结构清晰而稳定。它不影响普通导入,但能有效隔离内部实现,是构建高质量可维护库的重要机制。

熟练使用 __all__,意味着你不仅理解模块的组织方式,更真正理解了 Python 的“接口哲学”——清晰、显式、可控。

到此这篇关于Python中__all__用法及常见误区的文章就介绍到这了,更多相关Python __all__用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文