python中容易出錯的import機制
寫python程式碼時發現,正常執行main。py檔案沒問題,但一執行包package內的。py檔案,總是報import失敗的error。比如:
# 檔案結構
└──
D
:
\
workplace
\
python
\
import_test
├──
main
。
py
├──
pack1
│
├──
module1
。
py
│
├──
module2
。
py
│
├──
module3
。
py
# main。py
from
pack1
import
module1
# module1。py
import
pack1。module3
# module2。py
from
。
import
module3
直接執行main。py沒問題,但執行module1。py報錯
ModuleNotFoundError: No module named ‘pack1’
,執行module2。py報錯
ImportError: attempted relative import with no known parent package
。
之前一直不以為然,直到部署python專案時,又再次出現這樣的問題,參考一些資料,總結分享一下。
在瞭解 import之前,有兩個概念必須提一下:
模組
: 一個。py 檔案就是一個模組module
包
:__init__。py檔案所在資料夾就是包package(這是python2中的概念,python3引入了Namespace Packages名稱空間包,沒有__init__。py的資料夾也可以看成是包)
那麼__init__。py檔案有什麼用呢?當import包時,會優先執行__init__。py,可以執行初始化操作。
還記得python哲學嗎,一切皆是物件,模組和包也都是物件<class 'module'>,是物件,就會有屬性。
那模組有哪些屬性需要我們注意呢,可以透過dir內建函式返回物件的類成員名稱列表。
#test。py
a
=
1
def
func
():
pass
(
dir
())
# 沒有引數,則返回當前模組的名稱列表
結果列印:
[
‘__annotations__’
,
‘__builtins__’
,
‘__cached__’
,
‘__doc__’
,
‘__file__’
,
‘__loader__’
,
‘__name__’
,
‘__package__’
,
‘__spec__’
,
‘a’
,
‘func’
]
有意思的是變數a和函式func也成為了模型的類成員,因此當我們from test import a時,就相當於去訪問類成員test。a。
此外__name__也是我們經常會用到的類成員,當運行當。py檔案時,__name__將等於‘__main__’,因此常常用
if __name__==‘__main__’:
來包含,只有作為執行檔案時才會執行的程式碼。
一定要用一切皆是物件的思想去理解模組和包。
介紹完上面模組和包的概念後,我們來看python的import機制,兩種語句:
import 。。。 :後面只能是模組或包
from 。。。 import 。。。 :from 後面只能是模組或包,import 後面可以是模組,包,模組中的變數、函式、類。
還有一種特殊的寫法from 。。。 import * 表示模組或包中的類成員都匯入,結合文末講解的 __all__來配合使用。
那python怎麼匯入模組呢
1)搜尋路徑的順序
搜尋「內建模組」(built-in module):time,os,sys等等
搜尋 sys。path 中的路徑
而sys。path中路徑順序又如下:
當前執行的。py檔案所在目錄;
環境變數 PYTHONPATH中列出的目錄;
第三方庫site-packages:像conda,pip安裝的庫都在這裡;
所以自定義的檔名千萬不要與第三方庫、內建模組同名!
你可以print(sys。path)檢視你的搜尋路徑
2)相對和絕對的匯入方法
程式猿有兩種方法匯入模組,相對匯入和絕對匯入
無論絕對匯入還是相對匯入都有參照物。絕對匯入的參照物是
當前執行的入口.py檔案所在的資料夾
,相對匯入的參照物是
當前模組所在位置
。
2.1絕對匯入
絕對匯入沒什麼好說的,從最頂層開始寫,位置很清晰,但巢狀資料夾一多,路徑太長。
舉個例子吧,檔案結構如下:
└──
D
:
\
workplace
\
python
\
import_test
├──
main
。
py
├──
pack1
│
├──
module1
。
py
# main。py
from
pack1
import
module1
執行main。py,正常執行,此時絕對匯入的參照物是main。py所在目錄,即D:\workplace\python\import_test
現在把main。py移到package1目錄下
└──
D
:
\
workplace
\
python
\
import_test
├──
pack1
│
├──
module1
。
py
│
├──
main
。
py
# main。py
from
pack1
import
module1
執行main。py,
報錯
ModuleNotFoundError: No module named ‘pack1’
,因為此時絕對匯入的參照物是main。py所在目錄,即D:\workplace\python\import_test\pack1,不存在D:\workplace\python\import_test\pack1\pack1,自然報錯。
2.2相對匯入
在python中相對匯入,又分
隱式
相對匯入和
顯式
相對匯入。
舉個例子:
# 檔案結構
└──
D
:
\
workplace
\
python
\
import_test
├──
main
。
py
├──
pack1
│
├──
module1
。
py
│
├──
module2
。
py
# module1。py
import
pack1。module2
# 絕對匯入
import
module2
# 隱式相對匯入
from
。
import
module2
# 顯式相對匯入
# main。py
from
pack1
import
module1
# 絕對匯入
相對匯入以
。
開始,表示當前模組所在目錄。
注意隱式相對匯入常常會與顯式相對匯入混淆,所以自PEP328隱式相對匯入正式淘汰,也就是說import只能是絕對匯入。
相對匯入在兩種情況下不能使用:
執行入口的.py檔案不可以使用相對匯入
常常會報這樣的錯誤:
ImportError: attempted relative import with no known parent package
。當輸入命令列python main。py或者IDE執行main。py時,main。py中不可以使用相對匯入。因為python會把執行。py檔案的__name__改為“__main__”,此時就找不到__main__。pack1。
top-level頂層資料夾不能作為包
常常會報這樣的錯誤:
ValueError: attempted relative import beyond top-level package
。因為你把頂層資料夾當成包在使用。
什麼是top-level?當前執行入口.py檔案所在的資料夾,也就是絕對匯入的參照物
。不可以把top-level資料夾作為包package,自然也就不能匯入了。
# 檔案結構
└──
D
:
\
workplace
\
python
\
import_test
├──
main
。
py
├──
pack1
│
├──
module1
。
py
│
├──
module2
。
py
# module1。py
from
。。
import
module2
# 報錯!!!!此時。。是頂層資料夾D:\workplace\python\import_test,不可以把它作為包,自然就不能匯入它
# main。py
from
pack1
import
module1
# 絕對匯入
自此我們總結一下三種報錯:
ModuleNotFoundError: No module named 'pack1'
絕對匯入時,注意參照物是當前執行入口。py檔案所在的目錄
ImportError: attempted relative import with no known parent package
當前執行入口。py檔案不支援相對匯入
ValueError: attempted relative import beyond top-level package
頂層資料夾不能作為包,進行匯入
還有幾點注意事項
1.pycharm的最佳化
pycharm會把專案的根目錄新增到sys。path!
所以往往你在pycharm下執行沒有問題,但部署專案時,用命令列執行檔案卻報錯!
可以print(sys。path)分別看一下pycharm下和命令列執行
2。 import是在執行時匯入,不是編譯時
因此可以在程式碼的任何位置import,但是我們標準習慣是在檔案開頭寫import
3。import只匯入一次
import會自動幫你判斷,只匯入一次,如果已經匯入,則不再匯入。像c語言#include,就需要你自己判斷。
4.可以對import的變數進行修改,不是深複製!
例子:
# module1。py
a
=
[
1
]
# module2。py
from
module1
import
a
a
。
append
(
2
)
# main。py
import
module2
from
module1
import
a
a
。
append
(
3
)
(
a
)
# 結果為[1,2,3]
5. __all__有妙用
from 。。。 import * 時,模組或包內的變數、函式、類不想全部暴露,我們可以用__all__這個模組的類屬性來選擇哪些可以訪問,哪些不能訪問。
模組的__all__屬性預設是全部成員,包的__all__屬性為空,需要在__init__.py檔案中指定__all__!!!
# 檔案結構
└──
D
:
\
workplace
\
python
\
import_test
├──
main
。
py
├──
pack1
│
├──
module1
。
py
# module1。py
a
=
0
class
B
():
pass
def
fun
():
pass
__all__
=
[
‘a’
,
‘B’
]
# main。py
from
pack1。module1
import
*
(
a
)
(
B
)
(
fun
)
# 報錯,不能訪問
參考:
沒有50CM手臂:《面試官一個小時逼瘋面試者》之聊聊Python Import System?
藍莓的鏟屎官:[Python] 也整理一下萬惡的Python import機制