1. 程式人生 > 其它 >Python 中 import module 和 package 方法簡單記錄

Python 中 import module 和 package 方法簡單記錄

  Python 中以 .py 結尾的檔案實際上為一系列 Python 語句的集合,其被稱為Python 指令碼(scripts),當用戶通過 python test.py 命令執行該指令碼時,實際上是由 Python 直譯器從上至下逐行對其中的語句進行執行。對於大多數 Python 指令碼而言,在開始位置一般通過 import 語句來匯入其所需要的模組。這裡記錄一下 Python 模組和其匯入相關的內容,供學習和討論。主要內容來自 The Python Tutorial -> 6.Modules.

  module

  在 Python 中,可以將一系列的函式或變數定義放在一個 .py 檔案中,供其他檔案進行使用,這樣的 .py 檔案稱為一個模組(module). 模組的名字即為 Python 檔名,如 test.py 即對應 test 模組,在模組內部,可以通過變數 __name__ 來對模組名進行訪問。通過 import 關鍵字,在 Python 指令碼中匯入該模組相關名字,後續可通過匯入的名字進行呼叫。兩種常見的匯入模組中名字的方式如下所示。在一個 Python 直譯器的執行過程(session)中,每個模組只會被匯入一次。在匯入過程中,模組中的內容會按照從上到下的順序進行執行和解析。

    import test        #匯入 test 模組,此時將名字 test 引入當前的符號表中(從而可以引用)
    test.doSomething()    #通過 test 模組名使用其中定義的函式 doSomething

    from test import doSomething   #從 test 中引入 doSomething,注意此時 doSomething 被加入當前符號表(可直接引用),而 test 則不會被加入(無法直接引用)
    doSomething()

    from test import *    #從 test 中匯入所有的名字,除了以 '_' 開始的名字

  模組作為一個 .py 檔案,也可以作為指令碼檔案使用,當使用 Python test.py 的方式執行模組時,其模組名 __name__ 會被填充為 "__main__".

  使用者可以通過內建的 dir() 函式檢視模組中定義的名字,其一般有兩種用法:

    dir(test)    #檢視 test 模組中定義的所有名字
    dir()        #檢視當前已定義的所有名字,注意不包括內建定義的名字

    import builtins
    dir(builtins)    #檢視所有內建函式和變數的名字

  module 的搜尋順序

  當通過 import 語句匯入一個模組的名字時,Python 直譯器遵循一定的搜尋順序

,其基本順序為:

  1)首先搜尋 Python 自帶的模組;

  2)在 sys.path 變數規定的一系列目錄中尋找名為 模組名.py 的模組檔案。sys.path 一般被初始化為 a) 包含當前執行指令碼的當前目錄;b)PythonPATH 巨集定義的路徑;c)一般還包括 site-packages 目錄,由 site 模組處理;

  sys.path 的相關內容可以參考 sys.path.

  package

  Python 的包(package)可以被理解為一系列模組的集合。每個 Python 包需要包含有一個對應的 __init__.py 檔案,__init__.py 檔案可以為空,也可以用於執行 Python 包匯入時的初始化等操作。

    test/    #一個 test 包的定義
    __init__.py #必須包含有 __init__.py 檔案
    t1.py    #包中的模組
    t2.py

  當執行 package 的 import 時,Python 直譯器通過 sys.path 指定的路徑來搜尋包的路徑。使用者通過正常的 import 操作匯入包中的名字。

    import test.t1    #匯入名字 test.p1,後續通過該名字進行引用
    from test import t1 #同上,但匯入的名字為 p1

  不同的 import 語句對於 import 物件有不同的要求。

    from package1 import test #將名字 test 視為定義在包中的名字(如包中定義的函式和變數等)進行定位,若無法定位,則將其視為一個模組進行載入,若載入失敗,則會丟擲 ImportError 異常
  import package1.package2.test #語句要求除了最後一個名字 test 外,前面所有的名字均需要為 Python 包,最後一個名字可以為一個模組或一個包,但不能為定義的函式或變數。

  當通過 from test import * 語句匯入包中的模組時,若 __init__.py 中定義了 __all__ 變數,則 __all__ 變數中包含有的模組名會被匯入,若沒有定義 __all__ 變數,則上述匯入語句不會匯入 test 包的任何子模組,其僅將包名 test 匯入當前符號表,同時會匯入包中定義的變數和函式等名字(如__init__.py 檔案中包含的名字)

  當 Python 包被組織為目錄結構時,可以通過決定路徑或相對路徑來進行 import.相對路徑指定從 package 開始到具體模組名的路徑,而相對路徑以 "." 開始,相對於當前模組位置來進行路徑搜尋。

    from test import t1    #當 test 可以通過 sys.path 定位時,可通過絕對路徑進行匯入
    from . import t2    #在模組 t1 中可以通過相對路徑對 t2 進行匯入

  

  Python 指令碼的編譯

  為了提升 Python 直譯器載入模組的速度,模組的定義通常被編譯並快取,編譯好的模組檔案位於目錄 __pycache__ 目錄下,並以 module.version.pyc 的格式進行命名,module 對應模組名,verison 對應編譯資訊,.pyc 為對應的字尾。Python 通過檢查原始檔的修改日期和編譯的 version 資訊來決定是否需要進行重新編譯。

  問題

  當存在當前一個目錄結構的 python 工程時,如何從 main.py 中呼叫 func.py 中定義的函式?

    src/ #源目錄
        test/
            func.py #定義了一些函式
        main.py #執行指令碼

  1.對於 Python 3.3+ 的直譯器版本,可以直接在 main.py 中使用 import 語句,即 from test import func 語句可直接匯入 func 模組,進而使用 func 名字來使用 func.py 中定義的函式等內容。這是由於 Python 3.3+ 中允許將不包含 __init__.py 檔案的目錄視為一個包,其被稱為 namespace packages. 這種定義包的方式一般用於 namespace 的共享,不推薦平常使用。具體可參考:Is __init__.py not required for packages in Python 3.3+

  2.由於 module 的搜尋路徑中包含有 sys.path 中定義的路徑,故而可以將 test 目錄加入 sys.path 路徑中,從而使得直譯器在搜尋模組時可以直接定位 func.py. 即通過 sys.path.insert(0, './test/'); import func 即可。但該方法一般比較使用於小專案的構建和內部使用的 trick;

  3.將 test 作為一個包進行處理。在 Python 中,包含有 __init__.py 檔案的目錄即被視為一個 Python 包,之後即可通過對包中的模組匯入方式進行引用。故而可以在 test/ 目錄下加入 __init__.py 檔案,此時 test 被視為一個 Python 包,可以通過 import test.func as func 或者 from test import func 來使用 func 模組中定義的內容;

  參考

  IMPORTERROR: ATTEMPTED RELATIVE IMPORT WITH NO KNOWN PARENT PACKAGE

  Is __init__.py not required for packages in Python 3.3+

  關於 import module 和 from module import 可以參考:Use 'import module' or 'from module import'?