__all__ 明确导出列表是最直接有效的方式,它限定 from module import * 可见符号,需配合下划线命名、__init__.py 导入控制及打包配置排除私有模块。

用 __all__ 明确导出列表是最直接有效的方式
Python 模块默认会把所有不以下划线开头的公有名称都暴露出去,但 __all__ 能强制限定 from module import * 可见的符号。它不阻止直接导入(比如 import module.submod),但能显著降低误用概率,是 PEP 8 推荐的惯用法。
实操建议:
- 在子模块(如
mylib/utils/internal.py)顶部显式定义__all__ = [],让它空着——这样from mylib.utils.internal import *什么也得不到 - 若该子模块本就不该被外部直接引用,更推荐把它重命名为
_internal.py(加前导下划线),这是 Python 社区公认的“私有模块”信号 -
__all__只影响import *,对import mylib.utils.internal或from mylib.utils import internal无效,所以必须配合命名规范一起用
在父模块中不导入、不转发私有子模块
即使子模块本身没被 __all__ 放行,只要父模块(如 mylib/utils/__init__.py)里写了 from . import internal 或 from .internal import helper,外部仍可能通过 mylib.utils.internal 访问到它。
实操建议:
- 检查所有上级
__init__.py,删掉对目标子模块的导入语句 - 如果父模块需要内部使用该子模块,用局部导入:在函数或类内部写
from . import internal,而不是模块顶层导入 - 避免在
__init__.py中用import *,它会把子模块名直接拖进父模块命名空间
运行时抛错拦截非法访问(慎用)
想彻底封死路径?可以在子模块顶部加一段运行时检查,判断调用栈是否来自允许的模块。但这属于防御性过载,通常没必要,且容易被绕过。
实操建议:
- 仅在强约束场景(如 SDK 分发、插件沙箱)考虑,例如:
import inspect if not any("mylib/" in f.filename for f in inspect.stack()): raise ImportError("mylib._secret is private, do not import directly") - 注意
inspect.stack()开销较大,不能放在高频路径;而且单元测试、REPL 等环境可能触发误报 - 这种做法破坏了 Python 的“责任共担”哲学——模块作者应靠文档和命名提示,而非强行阻断
真正关键的是包结构与命名的一致性
Python 没有真正的私有模块访问控制机制,_ 命名 + __all__ + 文档说明才是实际有效的组合。最容易被忽略的点是:很多人只改了子模块名,却忘了同步清理 setup.py 的 packages 列表或 pyproject.toml 的 packages 配置——导致打包后仍把不该发布的模块塞进了安装包里。
务必确认:find_packages(exclude=["tests*", "examples*", "*._*"]) 或等效配置已排除所有带下划线前缀的子模块目录。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











