Python 模块 __all__:星号导入只带走一个名字,为什么其他属性仍然能直接访问
给模块写 __all__ = ['public_name'] 后,from 模块 import * 确实只带来清单中的名字。但普通模块对象仍然保有其他属性,显式点名导入也可以成功。__all__ 定义的是星号导入选择什么,不能用它来隐藏秘密或阻止访问。
下面把固定源码写入自动清理的临时目录,只在本次进程里加入搜索路径,实验结束移除路径和模块缓存。需要 Python 3.8 及以上。保存为 demo.py,运行 python demo.py;代码没有联网,也不接收外部源码。
AI模型生成概念图:完整陈列架与选出的展示框同时存在,选入展示框不表示其他物件消失;不是权限系统截图。
import importlib
import sys
from pathlib import Path
from tempfile import TemporaryDirectory
name = "batch29_palette_demo"
if name in sys.modules:
raise RuntimeError("demo module name is already in use")
source = """public_name = "teal"
helper_name = "coral"
_internal_name = "gold"
__all__ = ["public_name"]
"""
with TemporaryDirectory(prefix="module-exports-") as folder:
Path(folder, name + ".py").write_text(source, encoding="utf-8")
sys.path.insert(0, folder)
try:
module = importlib.import_module(name)
namespace = {}
exec("from batch29_palette_demo import *", namespace)
exported = sorted(k for k in namespace if k != "__builtins__")
assert exported == ["public_name"]
print("star import:", exported)
from batch29_palette_demo import helper_name, _internal_name
assert module.helper_name == "coral"
assert helper_name == "coral" and _internal_name == "gold"
print("direct attributes:", module.helper_name, module._internal_name)
print("explicit import:", helper_name, _internal_name)
del module.__all__
namespace = {}
exec("from batch29_palette_demo import *", namespace)
exported = sorted(k for k in namespace if k != "__builtins__")
assert exported == ["helper_name", "public_name"]
print("without __all__:", exported)
finally:
sys.path.remove(folder)
sys.modules.pop(name, None)
print("temporary module cleaned")清单改变的是导入方的名称选择
第一次输出 star import: ['public_name']。临时模块定义了三个值,但 __all__ 只列出 public_name,因此星号导入仅把它加入 namespace。exec 在这里运行的是写死的导入语句,用独立字典观察产生的名字;没有执行来自用户或网站的代码。
__builtins__ 是 exec 为执行环境准备的条目,示例在展示时排除它,避免把执行环境误认成模块导出的第四个名称。实际业务代码通常应显式导入所用名称,让读者和静态工具都看得出依赖来自哪里。
不在清单里,属性也不会消失
direct attributes 输出 coral gold,explicit import 也输出 coral gold。module.helper_name 与 module._internal_name 可以点号访问,明确写出名字的 from 导入同样成功。__all__ 没有删除这些名称,更没有给下划线名称增加访问控制。
单下划线主要传达内部使用约定;协作者应尊重接口边界,但代码层面仍可访问。因此密码、令牌或其他秘密不能因为不在 __all__ 中就被视为安全。这里的三个颜色字符串都是专为演示生成的普通文本。
没有__all__时才使用下划线默认规则
删除 module.__all__ 后,再做一次星号导入,结果变成 helper_name、public_name。默认情况下,模块中以下划线开头的名称不被星号导入,因此 _internal_name 留在模块里。前一次 namespace 不会被清空或自动同步,所以示例每轮都创建新的字典,以免旧导入结果混入判断。
如果显式清单包含下划线开头的现有名称,它也可以被星号导入;清单中的名字应确实存在,拼错不会自动变成可用属性。维护导出表时,最好把真实公开接口的导入作为测试,而不是只核对字符串列表看起来完整。
这个实验只讨论普通模块
代码使用临时 .py 文件,结束后清理了本次加入的路径与模块缓存。清理缓存不会让所有已持有的对象引用神奇失效;这里进程马上结束,没有把演示模块交给其他代码长期使用。
包的 __all__ 还会涉及子模块导入,不能直接把一个普通模块里的例子扩展成任意包布局。若正在设计库接口,优先在文档中说明稳定公开名称,用清晰的显式导入保持依赖可见;把 __all__ 用作导出约定的一部分,而不要把它当成沙箱或保密机制。
资料核对日期:2026年10月2日。本文固定输入示例在本地 Python 3.12.14 实际执行,退出码为0,预期结果检查通过;这是语言行为演示,不是生产压测。


