一、__all__ 到底是干嘛的?
一句话总结:__all__ 是一个写在 .py 文件里的列表,用来规定:当别人用 from 模块 import * 时,只能导入列表里写的这些内容,其他的都导不进来。
它的核心作用:
- 控制模块的对外暴露接口,只给用户用你想给的函数 / 类
- 隐藏内部实现细节,避免用户误调用私有函数
- 让代码更规范、更易维护(符合工程化设计思想)
二、最基础的用法(直接抄就能用)
1. 先看没有 __all__ 的情况(默认行为)
比如 tool.py:
python
# tool.py
def add(a, b):
return a + b
def mul(a, b):
return a * b
def _internal_calc(): # 内部用的函数,不想给别人用
return 100
VERSION = "1.0.0"
如果别人这么导入:
python
# main.py
from tool import *
print(add(2,3)) # 能拿到
print(mul(4,5)) # 能拿到
print(_internal_calc()) # 也能拿到!(这不是我们想要的)
print(VERSION) # 也能拿到
默认情况下,import * 会把模块里所有不以下划线开头的名字 全导进来,下划线开头的(比如 _internal_calc)不会导,但其他的全暴露了,很不安全。
2. 加上 __all__ 控制导入
我们在 tool.py 里加一行 __all__:
python
# tool.py
__all__ = ["add", "mul"] # 只允许导入这两个函数!
def add(a, b):
return a + b
def mul(a, b):
return a * b
def _internal_calc():
return 100
VERSION = "1.0.0"
再用 from tool import * 试试:
python
# main.py
from tool import *
print(add(2,3)) # ✅ 正常运行(在__all__里)
print(mul(4,5)) # ✅ 正常运行(在__all__里)
print(_internal_calc()) # ❌ 报错:NameError: name '_internal_calc' is not defined
print(VERSION) # ❌ 报错:NameError: name 'VERSION' is not defined
完美!只有 __all__ 列表里的 add 和 mul 能被 import * 导入,其他内容全被挡住了。
三、关键注意点(小白必看,避坑!)
1. __all__ 只对 from 模块 import * 生效!
这是最容易踩的坑:
- ✅
from tool import *:严格遵守__all__,只导列表里的 - ❌
import tool/from tool import add:完全不受__all__影响!
比如你依然可以这么写:
python
# main.py
import tool
print(tool._internal_calc()) # ✅ 能运行!__all__管不到这种导入方式
from tool import _internal_calc # ✅ 也能运行!
__all__ 不是强制的权限控制,只是一个 "约定" 和 "过滤" ,本质是给 import * 做白名单,不是给模块加锁。
2. __all__ 里只能写字符串,对应变量 / 函数 / 类的名字
python
# 正确写法
__all__ = ["add", "mul", "User", "CONFIG"] # 全是字符串,对应模块里的名字
# 错误写法(直接写函数名,会报错)
__all__ = [add, mul] # ❌ 语法错误,必须是字符串
3. 下划线开头的名字,本来就不会被 import * 导入
哪怕你把 _internal_calc 写进 __all__,import * 也不会导它,这是 Python 的默认规则:
python
__all__ = ["add", "_internal_calc"] # 写了也白写,_internal_calc还是导不进来
四、进阶用法:包(文件夹)里统一暴露接口
这正好对应你图里的第二个问题:包(文件夹)里如何统一暴露接口。
1. 场景需求
比如你有一个 utils 包,里面有多个 .py 文件:
my_project/
├── main.py
└── utils/
├── __init__.py
├── calc.py
└── string.py
calc.py里有add、mulstring.py里有reverse_str、upper_str
你希望用户不用写 from utils.calc import add,而是直接 from utils import add,一步到位,这就需要用 __init__.py + __all__ 来统一暴露。
也就是在__init__中统一使用__all__函数来使用
2. 实现步骤
① 先写好子模块
utils/calc.py:
python
def add(a, b):
return a + b
def mul(a, b):
return a * b
utils/string.py:
python
def reverse_str(s):
return s[::-1]
def upper_str(s):
return s.upper()
② 关键:在 __init__.py 里导入 + 写 __all__
utils/__init__.py:
python
运行
# 从子模块导入需要暴露的函数
from .calc import add, mul
from .string import reverse_str, upper_str
# 用__all__规定:from utils import * 只能导入这些
__all__ = ["add", "mul", "reverse_str", "upper_str"]
③ 主程序直接调用(超级简洁)
main.py:
python
# 直接从包导入,不用写子模块名!
from utils import add, reverse_str
print(add(2,3)) # ✅ 5
print(reverse_str("abc"))# ✅ cba
# 用*导入也完全正常
from utils import *
print(mul(4,5)) # ✅ 20
print(upper_str("hello"))# ✅ HELLO
五、工程化最佳实践
- **每个对外的模块 / 包,都写
__all__**明确对外接口,让用户一眼就知道该用什么,不该用什么。 __all__只放公共 API,内部函数一律不写 比如工具类的内部辅助函数、临时变量,都不要出现在__all__里。- 包的
__init__.py只做接口聚合,不写业务逻辑__init__.py就干一件事:把包里的子模块函数导进来,统一暴露,保持干净。 - 不要依赖
__all__做安全控制 它只是规范,不是权限,真正的私有函数还是要加下划线_来约定。
六、完整可运行示例(跟着敲一遍就会)
项目结构
plaintext
demo_all/
├── main.py
└── utils/
├── __init__.py
├── calc.py
└── string.py
1. utils/calc.py
python
运行
def add(a, b):
return a + b
def mul(a, b):
return a * b
def _private_helper():
return "我是内部函数,别用我"
2. utils/string.py
python
运行
def reverse_str(s):
return s[::-1]
def upper_str(s):
return s.upper()
3. utils/init.py
python
运行
from .calc import add, mul
from .string import reverse_str, upper_str
__all__ = ["add", "mul", "reverse_str", "upper_str"]
4. main.py
python
运行
# 方式1:直接从包导入需要的函数
from utils import add, reverse_str
print("add(2,3) =", add(2,3))
print("reverse_str('hello') =", reverse_str("hello"))
# 方式2:用*导入(受__all__控制)
from utils import *
print("mul(4,5) =", mul(4,5))
print("upper_str('world') =", upper_str("world"))
# 尝试导入内部函数(会报错)
# from utils import _private_helper # 取消注释会报错
运行结果
plaintext
add(2,3) = 5
reverse_str('hello') = olleh
mul(4,5) = 20
upper_str('world') = WORLD
七、一句话总结
__all__ = ["函数名1", "函数名2"]:给from 模块 import *做白名单,只导列表里的- 只对
import *生效,import 模块不受影响 - 包的
__init__.py用__all__统一暴露接口,让用户调用更简单
你现在对 __all__ 的用法完全理解了吗?要不要我再给你补一个类的 __all__ 用法 ,或者结合你之前的工程化会话项目,给你加一套完整的 __all__ 接口设计?