您當前的位置:首頁 > 文化

python中容易出錯的import機制

作者:由 內卷積 發表于 文化時間:2021-11-18

寫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

print

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

print

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

*

print

a

print

B

print

fun

# 報錯,不能訪問

參考:

沒有50CM手臂:《面試官一個小時逼瘋面試者》之聊聊Python Import System?

藍莓的鏟屎官:[Python] 也整理一下萬惡的Python import機制

標簽: __  py  匯入  import  main