学习笔记:理解 Python 包(package)的入口文件 __init__.py 的三个作用,以及 import 时常见踩坑。


前言

在 Python 项目里,你会经常看到名为 __init__.py 的文件。名字里的 __init__ 和类里的构造函数类似,表示「初始化」;前后双下划线是 Python 的魔术命名,不能随意改成别的名字。

例如项目结构:

src/
└── test/
    ├── __init__.py
    ├── myinit.py
    └── other.py

本文总结 __init__.py 的三个核心作用。


作用一:把目录标记为「包」,支持 from ... import

只有普通文件夹时,Python 不一定能按包的方式导入。在目录下放置 __init__.py 后,该目录会被识别为一个 包(package),其他代码就可以用 from ... import 引用其中的模块。

示例结构:

src/test/
├── __init__.py      ← 有它,test 才是包
├── myinit.py
└── other.py

myinit.py

def sayinit() -> str:
    return "init"

在其他文件中引用:

from src.test.myinit import sayinit

print(sayinit())  # 输出: init

要点:

  • __init__.py 可以是空文件,仅用于「声明这是包」
  • 导入路径:包名.模块名.对象名,例如 src.test.myinit.sayinit
  • 调用函数要带括号:sayinit(),写 sayinit 只会打印函数对象本身

作用二:定义「从包对外暴露什么」

可以在 __init__.py 里把子模块的内容「再导出」,让外部调用更简洁。

src/test/__init__.py

from .myinit import sayinit

__all__ = ["sayinit"]

外部就可以这样写:

from src.test import sayinit

print(sayinit())  # 输出: init

而不必写完整路径 from src.test.myinit import sayinit

关于 __all__

__all__ 是一个字符串列表,主要用来控制:

from src.test import *  # 只会导入 __all__ 里列出的名字
说明 细节
是否必须 不是。即使不写 __all__from src.test import sayinit 仍可用(只要在 __init__.py 里 import 了)
名字必须唯一 同一个名字不能出现两次
典型用途 声明包的「公开 API」

多个模块里有同名函数怎么办?

如果两个文件里都有 sayinit,不能直接写两个同名导出,后面的会覆盖前面的。

目录结构:

src/test/
├── __init__.py
├── myinit.py      → def sayinit(): return "init"
└── other.py       → def sayinit(): return "other"

正确写法:用 as 起别名

from .myinit import sayinit as sayinit_from_myinit
from .other import sayinit as sayinit_from_other

__all__ = ["sayinit_from_myinit", "sayinit_from_other"]

使用:

from src.test import sayinit_from_myinit, sayinit_from_other

print(sayinit_from_myinit())   # init
print(sayinit_from_other())    # other

错误写法(不要这样):

from .myinit import sayinit
from .other import sayinit          # ❌ 覆盖上一个

__all__ = ["sayinit", "sayinit"]     # ❌ 无意义

作用三:包被 import 时自动执行

当其他文件第一次 import 这个包(或包下的子模块)时,Python 会先执行 __init__.py 里的代码,再加载具体模块。

src/test/__init__.py

print("nihao")

from .myinit import sayinit

__all__ = ["sayinit"]

其他文件:

from src.test import sayinit

控制台会先输出:

nihao

然后才能正常使用 sayinit

执行顺序(简化)

from src.test import sayinit
    │
    ├─ 1. 执行 src/test/__init__.py   (print、from .myinit import ...)
    │
    └─ 2. 加载 src/test/myinit.py

重要特性

特性 说明
只执行一次 同一进程内,包首次 import 时执行,之后用缓存,不会重复执行
副作用要小心 不要在 __init__.py 里随意写 print、连数据库等,除非是有意设计
实际项目用法 例如在 src/config/__init__.pyload_dotenv(".env.dev"),保证读配置前先加载环境变量

常见踩坑

1. 混用两种 import 路径,导致 __init__.py 执行两次

import test                              # 模块名: test
from src.test.myinit import sayinit      # 模块名: src.test

Python 会把 testsrc.test 当成两个不同模块,于是 __init__.py 里的 print("nihao") 会打印两次。

建议: 同一项目统一用一种风格,例如都用 from src.test import ...

2. 打印函数忘了加括号

print(sayinit)    # <function sayinit at 0x...>
print(sayinit())  # init

3. import testfrom src.test import 不是一回事

取决于 sys.path 和项目结构,路径写法不一致容易重复加载或找不到模块。生产项目推荐以项目根目录为基准,统一使用 from src.xxx import ...


和类里的 __init__ 对比

类的 __init__ 包的 __init__.py
触发时机 创建实例 obj = MyClass() 第一次 import 包时
作用 初始化一个对象 初始化整个包
能否省略 可以(用默认构造) 可以(空文件),但常用来放包级逻辑

小结

__init__.py 的三个作用:

  1. 标记包:让目录可以被 from ... import 引用
  2. 对外暴露 API:在包级别 re-export,__all__ 控制 import *
  3. 包初始化:第一次 import 时自动执行(加载配置、创建单例等)

记住三句话:

  • 名字必须是 __init__.py,不能改
  • 同名导出用 as 区分
  • 整个进程里通常只执行一次,但不要用两种路径重复 import 同一个包

更多推荐