Python基础-26 __init__.py详解

1.概述

    在日常写代码的过程中,经常能在很多地方看到__init__.py文件,似乎感觉没有什么用,但有时候却又不得不存在这个文件,而文件里面的内容通常又是空,那这个文件到底有什么作用呢?今天让我们就来探究一下。

2.Python的模块和包

    在Python代码组织里面有两个基本单位模块(module)包(package),两者之间的定义如下所示:

  • 模块:简单来讲就是一个py文件。里面定义了变量、函数、类、方法等,示例如下所示:
# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     hello.py
# @Time      2025-08-24 15:29
# @Author:   Surpass

def hello(name:str="surpass")->str:
    return f"Hello, {name}!"

def nationality(nationality:str="China")->str:
    if nationality == "China":
        return "I am Chinese"
    elif nationality == "Japan":
        return "I am Japanese"
    elif nationality == "USA":
        return "I am American"
    else:
        return "I am Astronaut"

    使用方法如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     main.py
# @Time      2025-08-24 15:35
# @Author:   Surpass

import hello

if __name__ == '__main__':
    print(hello.hello())
    print(hello.nationality())

    输出结果如下所示:

Hello, surpass!
I am Chinese
  • 包:则是一个文件夹里面可以包含多个模块文件,即在一个文件夹里面可以包含多个py文件。示例如下所示:
├─calcuator
│   ├─add.py
│   ├─sub.py
│   ├─__init__.py

    使用方法如下所示:

from calcuator.add import add
from calcuator.sub import sub

if __name__ == '__main__':
    print(add(2, 3))
    print(sub(2, 3))

3.__init__.py文件在做什么

    要理解__init__.py有什么作用,可以先看看这个文件在做什么操作?拿以上示例为例,文件目录如下所示:

├─calcuator
│   ├─add.py
│   ├─sub.py
│   ├─__init__.py

    各文件内容如下所示:

# add.py
def add(a:int,b:int)->int:
    return a+b

# sub.py
def sub(a:int,b:int)->int:
    return a-b

# __init__.py
print("Call __init__")

# main.py
from calcuator.add import add
from calcuator.sub import sub

if __name__ == '__main__':
    print(add(2, 3))
    print(sub(2, 3))

    在main.py运行代码,其结果如下所示:

Call __init__
5
-1

    从输出结果,可以看出__init__.py中的代码也执行了。则可以说明,当从一个包里面调用时,__init__.py中的代码会被首先执行

4.__init__.py文件的作用

    虽然__init__.py文件大部分情况文件内容为空,但其功能非常灵活,常用用法如下所示。

4.1 标识Python包

    在模块所在文件夹里面添加__init__.py后,则IDE会自动将这个文件夹识别为Python包

4.2 简化模块导入操作

    假设包的目录结构如下所示:

├─calcuator
│   ├─add.py
│   ├─sub.py
│   ├─div.py
│   ├─mul.py
│   ├─__init__.py

    如果要使用包里面的所有方法,常规写法如下所示:

from calcuator.add import add
from calcuator.sub import sub
from calcuator.div import div
from calcuator.mul import mul

    如果不想每次都写这么麻烦,可以在__init__.py这样写

# __init__.py
from .add import add
from .sub import sub
from .mul import mul
from .div import div

print("Call __init__")

    则在其他地方引用和调用时,可以简写为如下形式

import calcuator

if __name__ == '__main__':
    print(calcuator.add(2, 3))
    print(calcuator.sub(2, 3))

4.3 批量导入

    如果一个模块里面包含有很多变量、方法,而在调用时,嫌麻烦不想一个个写,则可以在__init__.py文件中这样写

# add.py
from typing import List

def add_number(a:int,b:int)->int:
    return a+b

def add_str(a:str,b:str)->str:
    return a+b

def add_list(a:List[int],b:List[int])->List[int]:
    return a+b

# __init__.py
from .add import *

    在调用时,可以这样写

# main.py
import calcuator

if __name__ == '__main__':
    print(calcuator.add_number(2, 3))
    print(calcuator.add_list([2], [3]))

通过这种方法,就可以实现批量导入,但需要注意导入存在有类和方法同名的情况,则遵从后面导入的会覆盖先导入的

4.4 限定导入范围

    在模块导入时,需要遵从一个原则最小化导入原则,即需要使用哪些类、函数时,仅导入这些类、函数即可,尽可能避免使用 import * 这种形式,因此__init__.py又可以改写为这个样子

from .add import add_number

    这样改写后,在引用时,仅会引用add_number一个方法,而这样又会带来另一个问题,如果还要使用其他方法或模块,该怎么办呢?Python也提供了另一种方式,__init__.py可以改写为这种形式

# 通过 __all__ 显式定义了要导入的模块列表
__all__=[
    "add", # 对应于add.py
    "div", # 对应于div.py
    "sub"  # 对应于sub.py
]

print("Call __init__")

    在调用时,虽然了可以使用 import * ,但在__init__.py已经限定了引用的范围。

from calcuator import *

if __name__ == '__main__':
    print(add.add_str(2, 3))
    print(mul.mul(2, 3))          # __init__.py已经有限定,因此这里会出现报错

4.5 初始化操作

    在前面的示例,我们已经得知,在调用包时__init__.py会优先执行,因此也可以在里面进行一些初始化操作。示例如下所示:

# __init__.py

import platform
import os

# 通过__all__ 显式定义了要导入的模块列表
__all__=[
    "add", # 对应于add.py
    "div", # 对应于div.py
    "sub",  # 对应于sub.py
]

# 演示在 __init__.py 里进行初始化操作

if platform.system() == "Windows":
    os.makedirs(r"F:\python_test",exist_ok=True)
elif platform.system() in ["Darwin","Linux"]:
    os.makedirs(r"/tmp",exist_ok=True)
else:
    exit(255)

print("Call __init__")

4.6 版本管理

    在存在多人协作时,方便快速确认版本,示例代码如下所示:

# __init__.py
__version__ = "8.9.0"

print("Call __init__")

# main.py
import calcuator

if __name__ == '__main__':
    print(calcuator.__version__)

4.7 动态加载

    在一些大型项目中,如果包里面的模块太多,一个个写,很容易出现遗漏的情况,于是就出现动态导入的情况,示例如下所示:

# __init__.py
import os
import importlib

__version__ = "8.9.0"

current_path=os.path.dirname(__file__)

for module in os.listdir(current_path):
    if module.endswith(".py") and module != "__init__.py":
        module_name=module[:-3]
        import_module=f"{__name__}.{module_name}"
        print(f"import {import_module}")
        importlib.import_module(f"{__name__}.{module_name}")


print("Call __init__")

# main.py
import calcuator

if __name__ == '__main__':
    print(calcuator.__version__)
    print(calcuator.add.add_number(1,2))
    print(calcuator.mul.mul(10, 20))

    运行结果如下所示:

import calcuator.add
import calcuator.div
import calcuator.mul
import calcuator.sub
Call __init__
8.9.0
3
200

5. __init__.py最佳实践

    虽然__init__.py使用非常灵活,但也遵循以下建议:

  • 在项目中,一定添加这个文件,用以标识为python包
  • __init__.py同样可以添加代码,但尽量不要使用过多的逻辑,否则在导入包的时候会影响性能
  • 合理使用__all__从而避免暴露过多的内部细节
  • 动态加载功能很好,但也可能会带来性能问题
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容