python中的__all__ 具体用法

一、__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__ 列表里的 addmul 能被 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 里有 addmul
  • string.py 里有 reverse_strupper_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

五、工程化最佳实践

  1. **每个对外的模块 / 包,都写 __all__**明确对外接口,让用户一眼就知道该用什么,不该用什么。
  2. __all__ 只放公共 API,内部函数一律不写 比如工具类的内部辅助函数、临时变量,都不要出现在 __all__ 里。
  3. 包的 __init__.py 只做接口聚合,不写业务逻辑 __init__.py 就干一件事:把包里的子模块函数导进来,统一暴露,保持干净。
  4. 不要依赖 __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__ 接口设计?

相关推荐
王夏奇2 小时前
pycharm中3种不同类型的python文件
ide·python·pycharm
明湖起风了2 小时前
mqtt消费堆积
java·jvm·windows
Free Tester2 小时前
如何判断 LeakCanary 报告的严重程度
java·jvm·算法
小陈的进阶之路2 小时前
Selenium 滑动 vs Appium 滑动
python·selenium·测试工具·appium
Mike_6662 小时前
txt_json和xml_json
xml·python·json
大家的林语冰2 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
清心歌2 小时前
CopyOnWriteArrayList 实现原理
java·开发语言
zyq99101_13 小时前
DFS算法实战:经典例题代码解析
python·算法·蓝桥杯·深度优先
数据知道3 小时前
claw-code 源码分析:从 TypeScript 心智到 Python/Rust——跨栈移植时类型、边界与错误模型怎么对齐?
python·ai·rust·typescript·claude code·claw code