__all__仅控制from module import *的导入行为,不提供访问控制;它与单下划线命名共同构成API公开约定,但无法阻止显式导入或点号访问。

__all__ 为什么不能阻止 from module import * 以外的访问?
__all__ 只影响 from module import * 的行为,它不构成访问控制或封装机制。Python 没有真正的私有成员,下划线命名(如 _Helper)和 __all__ 都是约定,不是语法强制。
常见错误是以为把类名从 __all__ 删掉,外部就无法导入了——其实 import module; module._InternalClass 或 from module import _InternalClass 依然能成功。
-
__all__是给使用者的明确信号:“这些是公开 API” - IDE 和 linters(如 mypy、pylint)会依据它做补全和警告
- 文档生成工具(如 Sphinx)默认只收录
__all__中的成员 - 它对
dir(module)输出无影响——所有公有属性仍可见
如何正确定义 __all__ 并配合命名约定隐藏实现类?
把真正要暴露的类列在 __all__ 里,同时用单下划线前缀标记内部辅助类。这是最通用、最易被工具链识别的方式。
示例模块 utils.py:
立即学习“Python免费学习笔记(深入)”;
class _ConfigLoader:
def load(self): ...
<p>class DataProcessor:
def <strong>init</strong>(self):
self._loader = _ConfigLoader()</p><p>class Validator:
def validate(self): ...</p><p><strong>all</strong> = ["DataProcessor", "Validator"]-
_ConfigLoader不在__all__中,且带_前缀,明确表示非公开 - 用户执行
from utils import *时,只会导入DataProcessor和Validator - 若用户执意
from utils import _ConfigLoader,属于主动绕过约定,责任不在模块作者 - 避免使用双下划线(
__name)触发 name mangling——它用于类内“伪私有”,不适合模块级隐藏
__all__ 为空列表或缺失时会发生什么?
如果模块没定义 __all__,from module import * 会导入所有不以下划线开头的公有名称(即 dir(module) 中以字母开头、非 _ 开头的项)。
这很危险:一个临时变量 temp_result = ... 也可能被意外导出,污染使用者命名空间。
- 显式定义
__all__ = []表示“不导出任何东西”,from module import *将导入空集 - 更推荐写全需要导出的名称,而不是留空——否则使用者必须用
import module点号访问,体验差 - 一旦定义了
__all__,哪怕漏写一个本该公开的类,也会导致使用者from module import *找不到它,且不会报错,容易引发静默故障
与 __getattr__ 配合做动态控制是否必要?
极少数场景(如插件系统、懒加载模块)需要更精细控制,可结合 __getattr__ 抛出 AttributeError 来拦截未授权访问。但这属于进阶技巧,95% 的模块不需要。
例如:
def __getattr__(name):
if name in ["_InternalUtil", "_debug_mode"]:
raise AttributeError(f"{name!r} is not part of the public API")
raise AttributeError(f"module {__name__} has no attribute {name!r}")-
__getattr__在__all__之外提供一层运行时拦截,但增加复杂度 - 它不影响
dir()输出,也不影响静态分析工具,仅对点号访问生效 - 误配会导致
AttributeError泛滥,调试困难;建议只在明确需要拒绝访问时才用 - 不要和
__all__冲突——比如__all__放了A,__getattr__却拦掉A,会让使用者困惑
真正要隐藏的实现细节,靠 __all__ + _前缀 + 文档说明 就够了。过度防御反而让模块难用、难维护。


















