Python技术迷

python中_init_.py 到底有啥用?

你是不是也有过这种感觉:写 Python 项目的时候,目录里老是蹦出来一个 __init__.py,删了吧又有点慌,留着吧又不知道它到底有啥用,看着很“玄学”。

今天就用一篇通俗一点的文章,把这个文件讲明白。

1. 先搞清楚:__init__.py 是谁?

先看一眼常见目录结构:

myproject/
    main.py
    utils/
        __init__.py
        file.py
        math.py

这里的 utils 目录,只要里面有一个 __init__.py,Python 就会把它当成一个“包”(package),而不是普通文件夹。

简单说:

  • 有 __init__.py → 可以 import utils、from utils import xxx
  • 没有 __init__.py → 早期 Python 里就只是个普通目录,不能当包用

现在 Python 3 支持“命名空间包”,有时候没 __init__.py 也能当包用,但大多数项目和教程,还是习惯老老实实加一个,兼容性好、行为也直观。

2. import 的时候,__init__.py 会被执行

只要你写了下面这些代码之一:

import utils
from utils import math
from utils import *

Python 内部其实都会先去执行一遍 utils/__init__.py 里的代码,然后再决定给你返回什么。

也就是说,你在 __init__.py 里面写什么,它就会在“导入包”的那一刻跑一遍:

# utils/__init__.py

print("utils 包被导入啦")

VERSION = "1.0.0"

然后你在 main.py:

import utils

print(utils.VERSION)

运行结果大概是:

utils 包被导入啦
1.0.0

所以第一个结论:

__init__.py 就是“包被 import 时自动执行的一段 Python 脚本”。

它既能放变量、函数、类,也能放初始化逻辑。

3. 用 __all__ 控制 from xxx import *

有时候我们只想把包里的“少数几个东西”暴露给别人用,而不是把所有内部模块都扔出去,这时候就轮到 __init__.py 出场了。

比如你有这样一个工具包:

utils/
    __init__.py
    file.py      # 文件相关
    math.py      # 数学相关
    secret.py    # 一些内部函数,不想给别人用

file.py:

defread_file(path: str) -> str:
with open(path, "r", encoding="utf-8") as f:
return f.read()

math.py:

defadd(a, b):
return a + b

secret.py:

def_internal_debug():
    print("just for debug")

这时候我们在 __init__.py 里这样写:

# utils/__init__.py

from .file import read_file
from .math import add

__all__ = ["read_file", "add"]

外面的人就可以这么用:

from utils import *

print(read_file("a.txt"))
print(add(1, 2))

而 secret.py 里的东西,默认就不会被 from utils import * 带出去。

这里 __all__ 的意思就是:我这个包“官方对外公开”的 API 列表。 别的模块想强行导你内部的,也不是不行,但最起码一眼就能看出来:你这是在用人家的私货。

4. 把包当“门面”:对外简单,对内随便折腾

再举个稍微完整一点的小例子,我们写一个自己的“数学工具包”,对外只有 add / sub 两个函数,用起来很简单:

# 目录结构
mypkg/
    __init__.py
    _core.py
    _advanced.py

_core.py:

defadd(a, b):
return a + b

defsub(a, b):
return a - b

_advanced.py:

defmatrix_add(m1, m2):
# 假装这里有一堆矩阵相加的实现
return ...

注意两个内部模块都用下划线开头,表示“内部实现”。

__init__.py 里这么写:

# mypkg/__init__.py

from ._core import add, sub

__all__ = ["add", "sub"]
__version__ = "0.1.0"

外面只需要这样用:

from mypkg import add, sub

print(add(3, 5))
print(sub(10, 7))

至于你内部到底是 _core 还是 _advanced,以后要不要拆文件、重构实现,对调用方来说完全无感——这就是用 __init__.py 做“包门面”的常见玩法。

5. 包级初始化:日志、配置、懒加载都可以干

还有一个经常被忽略的用法:在包第一次被导入时做一些初始化工作。

比如你封装了一个 SDK,想在导入时自动配置日志:

# sdk/__init__.py

import logging

logger = logging.getLogger("my_sdk")
handler = logging.StreamHandler()
formatter = logging.Formatter("[SDK] %(asctime)s %(levelname)s: %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

__all__ = ["logger"]

使用方:

from sdk import logger

logger.info("hello")

又比如做“懒加载”:

# biglib/__init__.py

defheavy_function():
# 只有真正调用时才导入耗时模块
from ._heavy import really_heavy_function
return really_heavy_function()

这样的话,import 包本身就比较轻量,只有用到重型功能时,才会真正加载对应模块。

6. 现在还必须要写 __init__.py 吗?

严格一点地说:

  • Python 3 里,有一种叫“命名空间包”的东西,允许没有 __init__.py 的目录也被当成包用。
  • 一些现代框架(比如部分插件系统)会利用这个特性。

但实际工程里,更稳妥的做法还是:

只要你是一个“正经要给别人 import 的目录”,就老老实实放一个 __init__.py。

好处是:

  • 对 IDE / 静态检查工具更友好
  • 对老版本解释器更兼容
  • 看到 __init__.py 就知道:这是个包,不是随便丢的资源文件夹

所以总结到这儿,其实可以一句话概括:

__init__.py 决定了“这个目录是包”,并且在“包被导入时我想发生什么”。

理解了这个,再看到满屏的 __init__.py,大概就不会再觉得它是个神秘文件了,反而会想: “这里我是不是也该给自己的包设计一个‘门面’?”

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB