1.概述
? ? 在日常寫代碼的過(guò)程中,經(jīng)常能在很多地方看到__init__.py文件,似乎感覺(jué)沒(méi)有什么用,但有時(shí)候卻又不得不存在這個(gè)文件,而文件里面的內(nèi)容通常又是空,那這個(gè)文件到底有什么作用呢?今天讓我們就來(lái)探究一下。
2.Python的模塊和包
? ? 在Python代碼組織里面有兩個(gè)基本單位模塊(module)和包(package),兩者之間的定義如下所示:
- 模塊:簡(jiǎn)單來(lái)講就是一個(gè)
py文件。里面定義了變量、函數(shù)、類、方法等,示例如下所示:
# @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())
? ? 輸出結(jié)果如下所示:
Hello, surpass!
I am Chinese
- 包:則是一個(gè)文件夾里面可以包含多個(gè)模塊文件,即在一個(gè)文件夾里面可以包含多個(gè)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有什么作用,可以先看看這個(gè)文件在做什么操作?拿以上示例為例,文件目錄如下所示:
├─calcuator
│ ├─add.py
│ ├─sub.py
│ ├─__init__.py
? ? 各文件內(nèi)容如下所示:
# 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運(yùn)行代碼,其結(jié)果如下所示:
Call __init__
5
-1
? ? 從輸出結(jié)果,可以看出__init__.py中的代碼也執(zhí)行了。則可以說(shuō)明,當(dāng)從一個(gè)包里面調(diào)用時(shí),__init__.py中的代碼會(huì)被首先執(zhí)行。
4.__init__.py文件的作用
? ? 雖然__init__.py文件大部分情況文件內(nèi)容為空,但其功能非常靈活,常用用法如下所示。
4.1 標(biāo)識(shí)Python包
? ? 在模塊所在文件夾里面添加__init__.py后,則IDE會(huì)自動(dòng)將這個(gè)文件夾識(shí)別為Python包
4.2 簡(jiǎn)化模塊導(dǎo)入操作
? ? 假設(shè)包的目錄結(jié)構(gòu)如下所示:
├─calcuator
│ ├─add.py
│ ├─sub.py
│ ├─div.py
│ ├─mul.py
│ ├─__init__.py
? ? 如果要使用包里面的所有方法,常規(guī)寫法如下所示:
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__")
? ? 則在其他地方引用和調(diào)用時(shí),可以簡(jiǎn)寫為如下形式
import calcuator
if __name__ == '__main__':
print(calcuator.add(2, 3))
print(calcuator.sub(2, 3))
4.3 批量導(dǎo)入
? ? 如果一個(gè)模塊里面包含有很多變量、方法,而在調(diào)用時(shí),嫌麻煩不想一個(gè)個(gè)寫,則可以在__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 *
? ? 在調(diào)用時(shí),可以這樣寫
# main.py
import calcuator
if __name__ == '__main__':
print(calcuator.add_number(2, 3))
print(calcuator.add_list([2], [3]))
通過(guò)這種方法,就可以實(shí)現(xiàn)批量導(dǎo)入,但需要注意導(dǎo)入存在有類和方法同名的情況,則遵從后面導(dǎo)入的會(huì)覆蓋先導(dǎo)入的。
4.4 限定導(dǎo)入范圍
? ? 在模塊導(dǎo)入時(shí),需要遵從一個(gè)原則最小化導(dǎo)入原則,即需要使用哪些類、函數(shù)時(shí),僅導(dǎo)入這些類、函數(shù)即可,盡可能避免使用 import * 這種形式,因此__init__.py又可以改寫為這個(gè)樣子
from .add import add_number
? ? 這樣改寫后,在引用時(shí),僅會(huì)引用add_number一個(gè)方法,而這樣又會(huì)帶來(lái)另一個(gè)問(wèn)題,如果還要使用其他方法或模塊,該怎么辦呢?Python也提供了另一種方式,__init__.py可以改寫為這種形式
# 通過(guò) __all__ 顯式定義了要導(dǎo)入的模塊列表
__all__=[
"add", # 對(duì)應(yīng)于add.py
"div", # 對(duì)應(yīng)于div.py
"sub" # 對(duì)應(yīng)于sub.py
]
print("Call __init__")
? ? 在調(diào)用時(shí),雖然了可以使用 import * ,但在__init__.py已經(jīng)限定了引用的范圍。
from calcuator import *
if __name__ == '__main__':
print(add.add_str(2, 3))
print(mul.mul(2, 3)) # __init__.py已經(jīng)有限定,因此這里會(huì)出現(xiàn)報(bào)錯(cuò)
4.5 初始化操作
? ? 在前面的示例,我們已經(jīng)得知,在調(diào)用包時(shí)__init__.py會(huì)優(yōu)先執(zhí)行,因此也可以在里面進(jìn)行一些初始化操作。示例如下所示:
# __init__.py
import platform
import os
# 通過(guò)__all__ 顯式定義了要導(dǎo)入的模塊列表
__all__=[
"add", # 對(duì)應(yīng)于add.py
"div", # 對(duì)應(yīng)于div.py
"sub", # 對(duì)應(yīng)于sub.py
]
# 演示在 __init__.py 里進(jìn)行初始化操作
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 版本管理
? ? 在存在多人協(xié)作時(shí),方便快速確認(rèn)版本,示例代碼如下所示:
# __init__.py
__version__ = "8.9.0"
print("Call __init__")
# main.py
import calcuator
if __name__ == '__main__':
print(calcuator.__version__)
4.7 動(dòng)態(tài)加載
? ? 在一些大型項(xiàng)目中,如果包里面的模塊太多,一個(gè)個(gè)寫,很容易出現(xiàn)遺漏的情況,于是就出現(xiàn)動(dòng)態(tài)導(dǎo)入的情況,示例如下所示:
# __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))
? ? 運(yùn)行結(jié)果如下所示:
import calcuator.add
import calcuator.div
import calcuator.mul
import calcuator.sub
Call __init__
8.9.0
3
200
5. __init__.py最佳實(shí)踐
? ? 雖然__init__.py使用非常靈活,但也遵循以下建議:
- 在項(xiàng)目中,一定添加這個(gè)文件,用以標(biāo)識(shí)為python包
- 在
__init__.py同樣可以添加代碼,但盡量不要使用過(guò)多的邏輯,否則在導(dǎo)入包的時(shí)候會(huì)影響性能 - 合理使用
__all__從而避免暴露過(guò)多的內(nèi)部細(xì)節(jié) - 動(dòng)態(tài)加載功能很好,但也可能會(huì)帶來(lái)性能問(wèn)題