python中的import絕對(duì)導(dǎo)入與相對(duì)導(dǎo)入詳解
前言
在使用python做一些簡(jiǎn)單項(xiàng)目時(shí),import一般不會(huì)出現(xiàn)問題,但項(xiàng)目結(jié)構(gòu)復(fù)雜時(shí),import可能發(fā)生報(bào)錯(cuò)。
最常見的錯(cuò)誤是:ImportError: attempted relative import with no known parent package,要弄清楚問題本質(zhì),就需要了解import的工作機(jī)制了。
眾所周知,import分為絕對(duì)導(dǎo)入和相對(duì)導(dǎo)入,兩者的唯一區(qū)別就是是否使用了"." 為一探究竟,構(gòu)建以下項(xiàng)目目錄:
imp_test
├── entrance1.py
├── pakg1
│ ├── __init__.py
│ ├── module1.py
│ ├── module2.py
│ └── pakg1_1
│ └── __init__.py
└── pakg2
└── __init__.py
一、絕對(duì)導(dǎo)入
先明確下,python中modules的概念并不統(tǒng)一,官方文檔中的modules有時(shí)候指的是package,有時(shí)候指的是py文件,為了避免歧義,本文中module特指py文件,modules指“py文件以及包”。 形式如import XXX或from XXX import a,b 都是絕對(duì)導(dǎo)入。比如在module1.py中導(dǎo)入module2.py中的內(nèi)容:from module2 import v2。 當(dāng)執(zhí)行絕對(duì)導(dǎo)入時(shí),解釋器做了以下這些事情:
查找built-in modules(內(nèi)置模塊)。內(nèi)置模塊是python解釋器的一部分,用C語(yǔ)言編寫,常見的有os time sys json;
查找sys.path包含的目錄,包括:
- 啟動(dòng)文件所在的目錄
- 當(dāng)前項(xiàng)目的頂級(jí)目錄(注意:前提是使用IDE,在命令行直接import沒有這個(gè)目錄,可以理解為,在新建項(xiàng)目時(shí)IDE自動(dòng)將項(xiàng)目目錄添加到了sys.path中了)
- 當(dāng)前解釋器lib目錄,主要是第三方包的安裝目錄
因此可以得出結(jié)論:絕對(duì)導(dǎo)入時(shí),啟動(dòng)文件所在的目錄下以及項(xiàng)目頂級(jí)目錄下的包或py文件可以直接導(dǎo)入(此時(shí)IDE可以自動(dòng)補(bǔ)全)。值得注意的是,當(dāng)項(xiàng)目中涉及多個(gè)層級(jí)目錄下的py文件或模塊的互相導(dǎo)入,所有py文件的sys.path都是一樣的,絕對(duì)導(dǎo)入的搜索目錄針對(duì)的都是啟動(dòng)文件。 可以測(cè)試,當(dāng)啟動(dòng)文件為pakg1.module1.py,在pakg1.module1.py中執(zhí)行絕對(duì)導(dǎo)入,都沒問題:
#pakg1.module1.py import pakg1_1 # 當(dāng)前目錄下的包 import pakg2 # 頂層目錄下的包 import module2 # py文件
但是當(dāng)啟動(dòng)文件為entrance1.py時(shí),在entrance1.py執(zhí)行絕對(duì)導(dǎo)入:
#entrance1.py import pakg1.module1
就會(huì)提示在pakg1.module1.py中import module2報(bào)錯(cuò):
ModuleNotFoundError: No module named 'module2'
原因如上所述,啟動(dòng)文件為entrance1.py時(shí),module2并未在其搜索目錄中。
二、相對(duì)導(dǎo)入
帶".“的導(dǎo)入是相對(duì)導(dǎo)入,如:from .XXX import a,b,要理解相對(duì)導(dǎo)入,關(guān)鍵在于搞明白”."是什么含義。 在pakg1.module1.py執(zhí)行相對(duì)導(dǎo)入:
#module1.py
print('pakg1.module1 __name__ is:',__name__)
from .module2 import v2
print(v2)并啟動(dòng)pakg1.module1.py,得到報(bào)錯(cuò):
ImportError: attempted relative import with no known parent package
pakg1.module1 __name__ is: __main__
要理解"."的含義,關(guān)鍵在于理解python的__name__屬性:事實(shí)上python所有對(duì)象都有該屬性,包括包和py文件。對(duì)于py文件,這個(gè)屬性不是一成不變的:
- 當(dāng)其是啟動(dòng)文件時(shí),該文件的__name__ = "__main__";
- 當(dāng)其不是啟動(dòng)文件時(shí),__name__ = pakg_name.module_name,其中pakg_name從頂層目錄的包開始算起,有多層則以此類推;
而相對(duì)導(dǎo)入中的".“則指的是,該文件的__name__屬性去掉最后的文件名前面的部分,即該文件的“父包”,當(dāng)該文件是啟動(dòng)文件時(shí),由于__name__ = "__main__",此時(shí)“父包”為空,故會(huì)報(bào)錯(cuò)no known parent package。 事實(shí)上,python的modules還有一個(gè)屬性__package__,官方文檔解釋如下:

從以上文字我們可以了解到:對(duì)于包來講,__package__ == __name__,而對(duì)于py文件來講,__package__則代表其父包,并且對(duì)于在項(xiàng)目頂層目錄的py文件(對(duì)于啟動(dòng)文件,不管實(shí)際目錄怎樣,都認(rèn)為它是頂層文件),該屬性為空。因此我們可以認(rèn)為某文件中相對(duì)導(dǎo)入的”.",指的就是該文件的__package__屬性。
三、結(jié)論
弄明白原理后,在實(shí)際項(xiàng)目中涉及到import,可以總結(jié)以下幾條原則:
- 如果項(xiàng)目層級(jí)不是特別深,而結(jié)構(gòu)預(yù)計(jì)不會(huì)做很大調(diào)整,最保險(xiǎn)的導(dǎo)入方式是:統(tǒng)統(tǒng)使用絕對(duì)導(dǎo)入,并且從項(xiàng)目頂層包開始引入;
- 反之,當(dāng)項(xiàng)目層級(jí)很深,而結(jié)構(gòu)可能面臨調(diào)整時(shí),可以使用相對(duì)導(dǎo)入,但需要保證該文件不是啟動(dòng)文件;
- 對(duì)于項(xiàng)目頂層文件,無論其是不是啟動(dòng)文件,都不要使用相對(duì)導(dǎo)入,因?yàn)轫攲游募偕蠈邮琼?xiàng)目目錄,沒有父包了,即__package__ = ''。
四、關(guān)于__init__
為什么每個(gè)包要有一個(gè)__init__?首先是因?yàn)樗梢詷?biāo)志這是一個(gè)包,可以被import;其次,在實(shí)際項(xiàng)目中,我們需要對(duì)外暴露的內(nèi)容更多的是函數(shù)、類或變量,如果要導(dǎo)入這些對(duì)象,就需要from pakg_name.module_name import a,b,c,可能略顯冗長(zhǎng),而如果提前在包的__init__中將要暴露的對(duì)象導(dǎo)入,這些對(duì)象將屬于“包的子集”,導(dǎo)入時(shí)只需要精確到包就行了,即from pakg_name import a,b,c,除此之外,使用__all__還可以約束該包要暴露的內(nèi)容,有“白名單”效果。因此在__init__明確每個(gè)包要暴露的對(duì)象是很好的習(xí)慣。
到此這篇關(guān)于python中的import絕對(duì)導(dǎo)入與相對(duì)導(dǎo)入詳解的文章就介紹到這了,更多相關(guān)python中的import內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python應(yīng)用領(lǐng)域和就業(yè)形勢(shì)分析總結(jié)
在本篇文章總我們給大家整理了關(guān)于Python應(yīng)用領(lǐng)域和就業(yè)形勢(shì)分析以及圖文介紹,需要的朋友們可以參考下。2019-05-05
Python連接mssql數(shù)據(jù)庫(kù)編碼問題解決方法
這篇文章主要介紹了Python連接mssql數(shù)據(jù)庫(kù)編碼問題解決方法,本文方法同樣適用mysql、sqllite、mongodb等數(shù)據(jù)庫(kù),需要的朋友可以參考下2015-01-01
Python中的變量及簡(jiǎn)單數(shù)據(jù)類型應(yīng)用
這篇文章主要介紹了Python中的變量及簡(jiǎn)單數(shù)據(jù)類型應(yīng)用,簡(jiǎn)單的數(shù)據(jù)類型包括字符串和數(shù)字,更多詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-03-03
Python3.7 dataclass使用指南小結(jié)
本文將帶你走進(jìn)python3.7的新特性dataclass,通過本文你將學(xué)會(huì)dataclass的使用并避免踏入某些陷阱。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02
詳解python使用canvas實(shí)現(xiàn)移動(dòng)并綁定鍵盤
這篇文章主要為大家介紹了python使用canvas實(shí)現(xiàn)移動(dòng)并綁定鍵盤,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-12-12
Python全局變量關(guān)鍵字global的簡(jiǎn)單使用
python中g(shù)lobal關(guān)鍵字主要作用是聲明變量的作用域,下面這篇文章主要給大家介紹了關(guān)于Python全局變量關(guān)鍵字global的簡(jiǎn)單使用,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Python中ini配置文件讀寫的實(shí)現(xiàn)
本文主要介紹了Python中ini配置文件讀寫的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02

