__all__ 是模块内显式声明「哪些名字能被 from module import * 导入」的唯一方式;没定义时默认导入不以下划线开头的名字但不可靠,必须显式定义为顶层字符串列表,否则导致导入行为不确定、工具链误报及api不稳定。

直接说结论: __all__ 是模块内显式声明「哪些名字能被 from module import * 导入」的唯一方式;没定义 __all__ 时,import * 默认只导入不以下划线开头的公有名字,但行为不可靠,应始终显式定义。
为什么 __all__ 不是可选的「建议」而是必须的约束
很多人误以为 __all__ 只是文档提示,其实它是 Python 解释器强制执行的白名单。一旦定义,from module import * 就完全忽略模块里其他所有名字(包括没加下划线的),哪怕它们是函数或类。
- 没定义
__all__:Python 会扫描模块全局变量,自动收集所有不以_开头的名字 —— 但这个过程不检查是否真正可导出(比如动态生成的变量、import进来的名字可能被意外包含) - 定义了
__all__:解释器只认这个列表里的字符串,其余一概过滤,连__version__或logger这种常见公有变量也不会进导入结果 - 类型检查工具(如 mypy)和 IDE(如 PyCharm)也依赖
__all__做补全和错误提示,不设会导致误报或漏报
__all__ 必须是字符串列表,且只能出现在模块顶层
它不是注释,不是变量别名,也不是运行时计算的结果 —— 必须是字面量列表,且不能在函数、类或条件块里定义。
- ✅ 正确写法:
__all__ = ["load_config", "DEFAULT_TIMEOUT", "ConfigError"] - ❌ 错误写法:
__all__ = [name for name in dir() if not name.startswith("_")](动态生成,import * 时还没执行) - ❌ 错误写法:
if DEBUG: __all__ = [...](条件分支导致 import * 行为不稳定) - ❌ 错误写法:
__all__ = ("load_config",)(元组不行,必须是 list 或 tuple?等等 —— 实际上 tuple 也行,但官方文档明确要求「list」,且多数 lint 工具(如 flake8)会警告非 list)
示例模块 config.py:
__all__ = ["load_config", "DEFAULT_TIMEOUT", "ConfigError"] <p>DEFAULT_TIMEOUT = 30</p><p>class ConfigError(Exception): pass</p><p>def load_config(path): ...</p><p>def _parse_env(): ... # 不会被 import * 导入</p><p>import json # json 模块名不会被导入,除非显式写进 <strong>all</strong></p>
常见陷阱:__all__ 和 import 的交互关系
__all__ 控制的是「从本模块导出什么」,不影响「本模块内部怎么 import」。但它直接影响别人 from this_module import * 能拿到什么 —— 包括你 import 进来的名字。
- 想让别人通过
import *用到你import的东西?必须手动加进__all__,例如:from pathlib import Path后,要写__all__ = ["Path"] -
from .submodule import *不会自动合并到父模块的__all__,必须显式列出:__all__ = ["Path", "load_config"],不能靠子模块自己定义 - 如果模块里用了
import numpy as np,np不会自动进入import *结果 —— 即使你把它加进__all__,也得确保np在当前命名空间存在(即 import 语句已执行)
最易被忽略的一点:修改 __all__ 后,必须同步更新文档、类型注解和测试用例里的导入方式 —— 因为 import * 本身就不推荐用于生产代码,它的存在主要是为了兼容旧习惯或特定 DSL 场景,真正在意 API 稳定性的库,应该让人明确写 from module import func。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











