2015年8月5日 星期三

在 Python 匯入其他資料夾的模組

這個問題在網路上其實可以找到非常多的討論,好像很多人都遇到一樣的問題
花了一些時間研究,雖然還是搞不太懂怎麼樣用相對路徑匯入模組
不過至少純以專案內自己的絕對路徑,目前實驗是可以運作的~。

這裡主要的參考資料是 [1],我的實驗程式的架構如下:
~/
  python/
    module1/
      __init__.py
      x.py
    module2/
      __init.py
      y.py
    main.py
所有的程式碼都放在 ~/python 這個資料夾中,有兩個模組 module1 和 module2,並且 __init__.py 全都是空的。

以下是三個 .py 檔的內容。

main.py
from module1.x import ModuleX

print("main.py is running under %s" % __name__)
print("main.py: Call module X's class")
ModuleX().run()

module1/x.py
from module2.y import ModuleY

class ModuleX:
    def run(self):
        print("Module X is running under %s" % __name__)
        print("x.py: Call Module Y")
        ModuleY().run()

module1/y.py
class ModuleY:
    def run(self):
        print("Module Y is running under %s" % __name__)

要執行以上的程式時,會使用以下的指令:
python ~/python/main.py

簡要說明一下,Main.py 會去呼叫 module1 資料夾裡的 x.py 定義的類別
x.py 又會透過匯入(import)的方式,匯入另一個資料夾 module2 的模組 y.py,並呼叫 y.py 裡面定義的類別。
而本文的重點就是 x.py 如何匯入 y.py 了~這裡使用的方式是軟體內的絕對路徑
也就是以執行的進入點 main.py 作為最上層(top level)的模組根目錄
x 模組的位置就會是 module1.x、y 模組的位置則是 module2.y。

比較詳細的說明,或許可以參考看看 [2-3]
關鍵點好像在於 Python 有個 __name__ 的設定,根據官方文件 [3] 關於relative import 的說明,有著以下的特性:
Relative imports use a module's __name__ attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to '__main__') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
我目前的理解是,每個模組都有個隱藏的 __name__ 屬性,會記錄這個模組在模組的樹狀結構中的位置
這個屬性會在模組被匯入時,會自動填入模組的名稱與路徑
而如果模組是程式進入點,__name__ 屬性會被填入 "__main__",並且會成為樹狀結構中的樹根
這種狀況下,程式進入點的 .py 檔是沒有辦法匯入非自己的子模組的其他模組的~。
這也說明了,在 [2] 中的回應,為何網友 Andrew Clark 說 relative import 只能用在被匯入的模組上。

最後,上述的程式碼的執行結果如下:
main.py is running under __main__
main.py: Call module X's class
Module X is running under module1.x
x.py: Call Module Y
Module Y is running under module2.y
可以看出,main.py 的 __name__ 被設定為 "__main__",表示程式進入點以及模組樹的樹根
而以 main.py 為樹根的樹狀結構中,x.py 的 __name__ 即為 "module1.x"、y.py 的 __name 則為 "module2.y"。


參考資料:
  1. How to accomplish this relative import in python
  2. Ultimate answer to relative python imports
  3. PEP 0328 -- Imports: Multi-Line and Absolute/Relative

沒有留言: