終於搞懂了Python模塊之間的相互引用問題

摘要:詳細講解了相對路徑和絕對路徑的引用方法。

在某次運行過程中出現瞭如下兩個報錯:

報錯1: ModuleNotFoundError: No module named '__main__.src_test1'; '__main__' is not a package
報錯2: ImportError: attempted relative import with no known parent package 

於是基於這兩個報錯探究了一下python3中的模塊相互引用的問題,下面來逐個解析,請耐心看完。

好的,我們先來構造第一個錯,測試代碼結構如下:

|--- test_main.py
|--- src
  |--- __init__.py                                                              
    |--- src_test1.py
    |--- src_test2.py

src_test2.py 代碼

class Test2(object):
    def foo(self):
        print('I am foo')

src_test1.py 代碼,引用Test2模塊

from .src_test2 import Test2

def fun1():
    t2 = Test2()
    t2.foo()
if __name__ == "__main__":
    fun1()

此時運行 src_test1.py 報錯“No module named '__main__.src_test1'; '__main__' is not a package”

問題原因:

主要在於引用src_test2模塊的時候,用的是相對路徑".",在import語法中翻譯成"./",也就是當前目錄下,按這樣理解也沒有問題,那爲什麼報錯呢?

從 PEP 328 中,我們找到了關於 the relative imports(相對引用)的介紹

通俗一點意思就是,你程序入口運行的那個模塊,就默認爲主模塊,他的name就是‘main’,然後會將本模塊import中的點(.)替換成‘__main__’,那麼 .src_test2就變成了 __main__.src_test2,所以當然找不到這個模塊了。

解決方法:

因此,建議的做法是在 src同層級目錄創建 引用模塊 test_main.py(爲什麼不在src目錄下創建,待會下一個報錯再講),並引用src_test1模塊,代碼如下:

from src.src_test1 import fun1

if __name__ == "__main__":
    fun1()

那爲什麼這樣執行就可以了呢,其中原理是什麼呢?我是這樣理解的(歡迎糾正):test_main執行時,他被當做根目錄,因此他引用的src.src_test1 是絕對路徑,這樣引用到哪都不會錯,此時他的name=‘main’,當執行src_test1的時候,注意了此時test1的name是 src.src_test1,那麼在test1中使用的是相對路徑,查找邏輯是先找到父節點(src目錄),再找父節點下面的src_test2,因此可以成功找到,Bingo!

輔證:

構造一個例子,就可以理解上面的 執行目錄就是根目錄 的說法了,修改test1,使引用test_main:

from .. import test_main

報錯:ValueError: attempted relative import beyond top-level package

OK,那繼續構造第二個報錯:

上文中說過,解決main 的問題,就是創建一個模塊,來調用使用相對路徑的模塊,那麼爲什麼我不能在相同目錄下創建這個文件來調用呢?讓我們來測試下代碼:

創建test_src.py文件,代碼結構變更如下:

|--- test_main.py
|--- src
  |--- __init__.py                                                              
    |--- src_test1.py
    |--- src_test2.pys
    |--- test_src.py

test_src 代碼:

from src_test1 import fun1

if __name__ == "__main__":
    fun1()

執行報錯:ImportError: attempted relative import with no known parent package

問題原因:

當執行test_src時,按上文理解,此時執行文件所在的目錄爲根目錄,那麼引用test1的時候,需要注意的是,此時test1的name屬性不再是src.src_test1,因爲程序感知不到src的存在,此時他的絕對路徑是 src_test1,此時再次引用相對路徑查找的test2,同樣的步驟,需要先找到父節點,而此時他自己就是根節點了,已經沒有父節點了,因此報錯“no known parent package”。

解決方法:

此時爲了避免父節點產生矛盾,因此將test1中的引入去掉相對引用即可

from .src_test2 import Test2    -->    from src_test2 import Test2

繼續深入:

那使用相對路徑和絕對路徑,編譯器是怎麼找到這個模塊的呢?

執行import的時候,存在一個引入的順序,即優先查找執行目錄下有沒有此文件,如沒有,再查找lib庫下,如還沒有,再查找sys.path中的路徑,如再沒有,報錯。

所以不管是當前目錄,還是 sys.path中的目錄,都可以查到 src_test2這個模塊,就可以編譯成功。

號外

解決完上述問題後,不管我們用哪種方式,我們調試代碼時,都是單個文件調試,但此時根目錄就不對了,import方式又要改動,執行起來很麻煩,所以這裏推薦另一種方式(有更好的方式歡迎留言),使用sys.path.append()的方法

import sys,os
sys.path.append(os.getcwd())
from src.src_test2 import Test2

使用append的方式,將程序文件根目錄放進了sys.path中,然後再引用絕對路徑,這樣的方式,不管使用上文中的第一或第二執行方式都可以調用,也可以單獨編譯test1文件,不用修改import路徑,也是相對安全的方式。但是缺點就是,如果你修改了某一個包名,需要將所有引用地方都修改一下,工作量大,所以因地制宜。

綜上,詳細講解了相對路徑和絕對路徑的引用方法,現在你應該對import導入的問題有了清晰的理解吧

備註:本文基於Python3.7版本測試

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

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