文章目录
在 Python 开发中,你可能见过这样的代码:
__all__ = ["add", "subtract", "multiply"]
这个神秘的 __all__ 到底是干什么用的?为什么有些模块有它,有些没有?今天我们来彻底搞懂这个东西。
什么是 __all__
__all__ 是一个模块级的特殊变量,它是一个字符串列表,定义了当使用 from module import * 时哪些名称会被导入。
简单来说,它就是模块的"出口清单"。
基本用法
先看个最简单的例子:
# mathutils.py
__all__ = ["add", "multiply"]
def add(x, y):
return x + y
def subtract(x, y): # 注意:这个函数不在 __all__ 中
return x - y
def multiply(x, y):
return x * y
def _internal_helper(): # 私有函数
pass
现在在另一个文件中:
from mathutils import *
print(add(2, 3))
print(multiply(2, 3))
print(subtract(2, 3))
输出:
5
6
Traceback (most recent call last):
File "/Users/shaowenshuang/SourceCode/test-all-str/main.py", line 5, in <module>
print(subtract(2, 3))
^^^^^^^^
NameError: name 'subtract' is not defined
虽然 subtract 函数是公开的(没有下划线开头),但因为它不在 __all__ 列表中,所以不会被 import * 导入。
在包中的应用
__all__ 在包(Package)中更有用处。假设我们有这样的包结构:
sound/
effects/
__init__.py
echo.py
surround.py
reverb.py
情况1:没有定义 __all__
# sound/effects/__init__.py
from . import echo
from . import surround
# 注意:reverb 没有被导入
def package_info():
return "Sound effects package v1.0"
使用时:
#!/usr/bin/env python3
"""
简单证明:使用 dir() 查看 from sound.effects import * 导入了什么
"""
print("导入前:")
_before = set(dir())
print(f"当前命名空间: {[name for name in _before if not name.startswith('_')]}")
print("\n执行: from sound.effects import *")
from sound.effects import *
print("\n导入后:")
_after = set(dir())
new_names = [name for name in _after if name not in _before]
print(f"新增的名称: {new_names}")
print(f"\n可用的模块/函数:")
for name in new_names:
if not name.startswith('_'):
obj = globals()[name]
if hasattr(obj, '__name__') and hasattr(obj, '__file__'):
print(f" {name}: 模块 ({obj.__file__})")
elif callable(obj):
print(f" {name}: 函数")
else:
print(f" {name}: {type(obj).__name__}")
输出:
导入前:
当前命名空间: []
执行: from sound.effects import *
导入后:
新增的名称: ['AUTHOR', 'VERSION', 'echo', 'package_info', 'surround']
可用的模块/函数:
AUTHOR: str
VERSION: str
echo: 模块 (/path/to/sound/effects/echo.py)
package_info: 函数
surround: 模块 (/path/to/sound/effects/surround.py)
情况2:定义了 __all__
# sound/effects/__init__.py
__all__ = ["echo", "reverb"] # 明确指定导出列表
from . import echo
from . import surround
from . import reverb
def package_info():
return "Sound effects package v1.0"
使用情况1的脚步验证,输出:
# 只可用:echo, reverb
# 不可用:surround, package_info (虽然存在,但不在 __all__ 中)
导入前:
当前命名空间: []
执行: from sound.effects import *
导入后:
新增的名称: ['echo', 'reverb']
可用的模块/函数:
echo: 模块 (/path/to/sound/effects/echo.py)
reverb: 模块 (/path/to/sound/effects/reverb.py)
有趣的覆盖行为
__all__ 还有一个有趣的特性:本地定义的名称可以覆盖导入的模块。
# sound/effects/__init__.py
__all__ = ["echo", "surround", "reverse"]
from . import echo
from . import surround
# 假设还有一个 reverse.py 文件
def reverse(text): # 本地定义的函数
"""这个函数会覆盖 reverse.py 模块"""
return text[::-1]
当使用 from sound.effects import * 时,reverse 指向的是这个本地函数,而不是 reverse.py 模块。
没有 __all__ 时的复杂行为
这里有个容易混淆的地方。当包没有定义 __all__ 时,from package import * 的行为比较特殊:
- 不会自动导入包目录下的所有
.py文件 - 只导入在
__init__.py中明确定义或导入的名称 - 如果某个子模块之前通过其他方式导入过,也会被包含
看这个例子:
# 先单独导入一个模块
import sound.effects.reverb
# 再使用 import *
from sound.effects import *
# 现在 reverb 也可以使用了,即使 __init__.py 中没有导入它
这是因为 reverb 已经在 sys.modules 中了。
实际开发建议
1. 别用 import *
虽然 __all__ 主要是为 import * 服务的,但在实际项目中强烈建议避免使用 import *:
# 不推荐
from mymodule import *
# 推荐
import mymodule
from mymodule import specific_function
原因:
- 命名空间污染
- 代码可读性差
- 容易产生命名冲突
- 静态分析工具难以处理
2. 用作 API 声明
即使不用 import *,__all__ 仍然有价值——它是模块公开 API 的正式声明:
# api.py
__all__ = [
"User",
"create_user",
"authenticate",
"get_user_profile"
]
class User:
pass
def create_user(username, email):
pass
def authenticate(username, password):
pass
def get_user_profile(user_id):
pass
def _hash_password(password): # 内部函数,不在公开 API 中
pass
def _validate_email(email): # 内部函数,不在公开 API 中
pass
这样其他开发者一看 __all__ 就知道这个模块提供哪些公开接口。
3. 工具支持
很多开发工具会识别 __all__:
- IDE 在代码补全时会优先显示
__all__中的名称 - 文档生成工具 会根据
__all__生成 API 文档 - 静态分析工具 会检查
__all__的正确性
实战案例
让我们看一个更复杂的真实案例:
# database/__init__.py
"""数据库操作包"""
__all__ = [
# 连接相关
"connect",
"disconnect",
"get_connection",
# 用户操作
"User",
"create_user",
"get_user",
"update_user",
"delete_user",
# 异常类
"DatabaseError",
"ConnectionError",
]
from .connection import connect, disconnect, get_connection
from .models import User
from .operations import create_user, get_user, update_user, delete_user
from .exceptions import DatabaseError, ConnectionError
# 包级别的配置
DEFAULT_TIMEOUT = 30
DEBUG_MODE = False
def _internal_setup():
"""内部初始化函数,不对外暴露"""
pass
_internal_setup()
使用者可以这样导入:
# 推荐的方式
from database import User, create_user, get_user
# 或者
import database
user = database.create_user("alice", "alice@example.com")
# 虽然可以工作,但不推荐
from database import *
小结
__all__ 是 Python 模块系统的一个重要特性:
- 控制导出:指定
from module import *导入的内容 - API 声明:明确模块的公开接口
- 工具友好:为 IDE 和其他工具提供信息
记住几个要点:
- 定义
__all__时要包含所有想要公开的名称 - 不在
__all__中的名称不会被import *导入 - 尽量避免使用
import *,但可以用__all__作为 API 声明 - 包的
__all__只控制直接在__init__.py中定义或导入的名称
合理使用 __all__ 可以让 Python 代码更加清晰和专业。

1774

被折叠的 条评论
为什么被折叠?



