Python 的 from module import * 会把哪些名字带入当前命名空间?答案不只取决于函数和变量是否存在,还取决于模块是否声明了 __all__。理解这套规则,不只是为了答对通配符导入的问题,更重要的是明确模块和包的公开边界,让调用方知道哪些名字可以稳定依赖。
通配符导入到底会导入什么
当模块没有定义 __all__ 时,from module import * 通常会导入所有不以下划线开头的名字。以下划线开头的名字会被视为内部实现细节。
# metrics.py
DEFAULT_WINDOW = 60
def average(values):
return sum(values) / len(values)
def _validate(values):
if not values:
raise ValueError("values must not be empty")
执行:
from metrics import *
print(DEFAULT_WINDOW) # 60
print(average([2, 4, 6])) # 4.0
print("_validate" in globals()) # False
下划线只是一种默认过滤规则,不是访问控制机制。调用方仍然可以显式导入内部名字:
from metrics import _validate
因此,Python 的“私有”主要依靠约定,而不是语言层面的强制隔离。
__all__ 是一份明确的导出清单
定义 __all__ 后,通配符导入会按照这份字符串列表选择名字。它既能隐藏普通名称,也能公开以下划线开头的名称。
# reports.py
__all__ = ["build_report", "ReportError"]
class ReportError(Exception):
pass
def build_report(rows):
if not rows:
raise ReportError("rows must not be empty")
return {"count": len(rows)}
def normalize_rows(rows):
return [dict(row) for row in rows]
_INTERNAL_VERSION = 2
现在运行下面的代码:
from reports import *
print(build_report([{"id": 1}, {"id": 2}]))
print(ReportError)
print("normalize_rows" in globals()) # False
print("_INTERNAL_VERSION" in globals()) # False
这里的关键边界是:__all__ 主要控制 from reports import *。它不会阻止以下用法:
import reports
print(reports.normalize_rows([{"id": 1}]))
它也不会让未列出的名字变成真正不可访问的私有成员。更准确地说,__all__ 是模块作者发布的 API 清单,而不是权限系统。
在包级别提供稳定入口
__all__ 在包的 __init__.py 中尤其有价值。可以把分散在多个子模块中的常用对象重新导出,让用户只依赖一个稳定入口。
可以这样实践,创建一个最小项目:
example/
├── demo.py
└── toolkit/
├── __init__.py
├── errors.py
└── formatting.py
toolkit/formatting.py:
def format_name(first, last):
return f"{first.strip().title()} {last.strip().title()}"
def _clean(value):
return value.strip()
toolkit/errors.py:
class InvalidNameError(ValueError):
pass
toolkit/__init__.py:
from .errors import InvalidNameError
from .formatting import format_name
__all__ = ["format_name", "InvalidNameError"]
demo.py:
from toolkit import InvalidNameError, format_name
def main():
try:
print(format_name(" ada", "lovelace "))
except InvalidNameError as exc:
print(f"invalid input: {exc}")
if __name__ == "__main__":
main()
在 example 目录中运行:
python demo.py
预期输出:
Ada Lovelace
调用方不必知道 format_name 位于 formatting.py。以后即使内部模块重新组织,只要 toolkit 的公开导入路径保持不变,使用方通常不需要修改代码。
需要注意,包的 __all__ 只列出名称还不够。相应名称必须已经在包命名空间中存在,因此示例先执行了 from .formatting import format_name,再把 format_name 写入 __all__。
不要让 __all__ 变成重复维护的陷阱
公开 API 最适合使用显式、静态的列表:
__all__ = [
"Client",
"ClientError",
"connect",
]
不建议根据 globals() 动态生成导出列表。动态写法可能意外公开导入的依赖、辅助常量或未来新增的内部对象,也会让代码审查者难以判断兼容性变化。
维护时可以检查以下事项:
__all__中的每个字符串都对应真实存在的名称。- 删除或重命名已公开名称时,按 API 破坏性变更处理。
- 内部帮助函数使用前导下划线,同时不要放入
__all__。 - 包级
__init__.py只重新导出真正需要稳定支持的对象。 - 业务代码尽量使用显式导入,避免
import *引发名称冲突和来源不明。
__all__ 的价值不在于鼓励通配符导入,而在于迫使模块作者回答一个关键问题:这个模块承诺长期支持哪些名字?当答案被写进代码,模块重构、文档生成和兼容性审查都会更清晰。