python中__init__.py是幹啥用的(what is the __init__.py file for)?

參考:https://stackoverflow.com/questions/448271/what-is-init-py-for

Python定義了兩種類型的包,常規包(regular package)和命名空間包(namespaces package)。常規包是傳統的包,因爲它們存在於Python 3.2和更早的版本中。一個常規的包通常被實現爲一個包含一個_init_ .py文件的目錄。當導入常規包時,將隱式地執行此_init_ .py文件,並且它定義的對象將綁定到包的名稱空間中的名稱。py文件可以包含任何其他模塊可以包含的相同的Python代碼,Python將在導入模塊時向模塊添加一些額外的屬性。

也就是說python3.2以及更早之前的版本,__init__.py是包中必需的一部分,這種包是regular package。3.2以後的包也就是namespaces package中,__init__.py不再是必須存在的。

那麼針對python3.2之前的版本,__init__.py是幹啥用的呢?

1、_init_ .py的文件可以將磁盤上的目錄標記爲Python包目錄。

比如你的磁盤上有如下目錄,並且mydir/是在你的search path

mydir/spam/__init__.py
mydir/spam/module.py

那麼你就可以使用

import spam.module

或者

from spam import module

來導入/mydir/spam/module.py中的代碼,如果你刪掉__init__.py文件,Python解釋器將不再在該目錄中查找子模塊,因此導入模塊的嘗試將失敗。

2、__init__.py文件通常是空的,但是我們也可以在其中放一些import語句,使使用者可以用更方便的名稱導出包的選定部分,保存方便的函數,等等。比如上面的例子

如果module.py中含有函數

def testdd():
    print("xx")

假設此時__init__.py是空的,那麼我們想調用moduly.py中的testdb函數,需要這麼做

from spam import module

module.testdd()

但是我們可以將module隱藏起來,在__init__.py中做如下聲明

from module import testdd

此時我們想調用testdd函數就可以這麼做

import spam
spam.testdd()

這樣的話有個好處就是其他用戶不需要知道函數在包層次結構中的確切位置。

3、前面我們提到當導入常規包時,將隱式地執行此_init_ .py文件,並且它定義的對象將綁定到包的名稱空間中的名稱。基於此特性,我們可以在__init__.py中定義包級別任何變量,並且做一些初始化的工作。

比如我自己的項目中__init__.py中的定義:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-l', dest='log_file', default='/log/xxx.log')
args = parser.parse_args()
log.init_log(args.log_file)

此段代碼裏做的事是解析命令行參數,進行日誌文件的設置和其他的初始化操作。非常方便。

 

4、第2點中我們提到可以在__init__.py文件中設置import語句,其實除了這個import語句,我們還經常看到__all__ = []這樣的定義。那麼__all__這個是幹什麼的呢?

 

①、其實__all__主要在模塊級別暴露接口。

Python 沒有原生的可見性控制,其可見性的維護是靠一套需要大家自覺遵守的”約定“,比如,下劃線開頭的變量對外部不可見。

形如__all__ = ["foo", "bar"],__all__ 是針對模塊公開接口的一種約定,以提供了”白名單“的形式暴露接口。如果定義了__all__,其他文件中使用from xxx import *(注意是*,模糊引用,如果是直接引用還是可以的)導入該文件時,只會導入 __all__ 列出的成員,可以其他成員都被排除在外。

比如我們在第2點提到的例子,假設module.py中還有一個函數testmm,此時我們在其中加入__all__ = ['testdd']。

__all__ = ['testdd']

def testdd():
    print("dd")

def testmm():
    print("mm")

此時我們在另一個文件中作如下調用:

可以發現testmm是無法引用的。(注:from module import  testmm 可以)

所以說python不提倡用 from xxx import * 這種寫法。如果一個模塊 xxx 沒有定義 __all__,執行 from spam import * 時會將 xxx 中非下劃線開頭的成員(包括該模塊import的其他模塊成員)都會導入當前命名空間,這樣就可能弄髒當前的命名空間。顯式聲明瞭 __all__,import * 就只會導入 __all__ 列出的成員,如果 __all__ 定義有誤,還會明確地拋出異常,方便檢查錯誤。

②、第①點中我們是在module.py中定義的__all__,也就是模塊級別暴露接口,其實我們也可以在__init__.py中定義__all__,也就是在包級別暴露。

比如你的磁盤上有如下目錄,並且mydir/是在你的search path上。

mydir/spam/__init__.py
mydir/spam/module.py
mydir/spam/test.py

module.py文件中代碼爲:

def testdd():
    print("module testdd")

test.py中代碼爲:

def testdd():
    print("test testdd")

__init__.py中代碼爲:

__all__ = ["module"]

此時在創建文件mydir/testdb.py,內容如下,此時test將無法被引用。

from spam import *

module.testdd()

#引用報錯,無法應用test
test.testdd()

但是當修改__init__.py中代碼爲:

__all__ = ["module", "test"]

test.testdd將正常引用。

③、__init__.py中定義__all__,爲 lint 等代碼檢查工具提供輔助。

編寫庫時,經常會在 __init__.py 中暴露整個包的 API,而這些 API 的實現可能是在包的其他模塊中。如果僅僅這樣寫:from xxx import a, b,一些代碼檢查工具,如 pyflakes 會報錯,認爲變量 a和 b import 了但沒被使用。一個可行的方法是把這個警告壓掉:from xxx import a, b # noqa (No Q/A,即無質量保證),但更好的方法是顯式定義 __all__,這樣代碼檢查工具就會理解,從而不再報 unused variables 的警告。

總結一下:

__init__.py主要有下面幾個用處:

1、標識一個python包(python 3.2之前的版本)

2、在其中放一些import語句,使使用者可以用更方便的名稱導出包的選定部分,保存方便的函數。

3、在其中做一些初始化工作(比如logging的設置等)

4、結合__all__,可以指定要導出的子模塊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章