深入瞭解import聲明

import聲明即用於導入模塊,比如import numpy as np,但是涉及複雜工程目錄時容易搞得稀裏糊塗,於是我專門使用了python3.7來測試並解決import相關問題。

基本定義

  • module:即模塊,也就是中各種.py文件,模塊名就是文件名

  • built-in-module:即內置模塊,就是在安裝python的時候系統編譯在python解釋器中的,比如numpy

  • package:任何包含__init__.py文件的文件夾就是一個package。注意根據python document,在python3.3以上中,即使沒有__init__.py,文件夾也被自動視作一個package

  • object:即對象,在python中,對象可以是函數,類以及變量等。

import與sys.path

如果import導入了一個module,就能運行該module中的所有代碼。導入package時,會運行package目錄下的__init__.py,然後通過__init__.py運行package目錄下的所有module,__init__.py可以是空的,前面講到在python3.3以上,__init__.py可以沒有。

import會自動在sys.path包含的目錄中尋找相應的模塊或者包,在一個.py腳本被運行時,sys.path會初始化包含以下目錄:

  • PYTHONPATH,即常說的系統環境變量PATH
  • 默認安裝的模塊目錄,比如numpy就在此目錄中
  • .py腳本所在的目錄,這一點根據腳本所在的目錄不同是可以變化的。

常用的import方式

常用的import方式有四種:

import <package>

import <module>

from <package> import <module or subpackage or object>

from <module> import <object>

當然還有

import <module> as   # 比如 import numpy as np
import <object> as

場景1:導入系統以及同級目錄下的模塊

對於以下的工程目錄:

test/                      # 跟目錄
    packA/                 # package packA
        subA/              # subpackage subA
            __init__.py
            sa1.py
            sa2.py
        __init__.py
        a1.py
        a2.py
    packB/                 # package packB (implicit namespace package)
        b1.py
        b2.py
    other.py
    start.py

要在start.py中導入系統numpy模塊,以及同級目錄下的other模塊,只需要:

import numpy
import numpy as np # 通常將numpy重命名爲np
import other

然後運行start.py腳本

場景2:導入子目錄下的模塊

如果需要在start.py中導入a1.pyb1.py以及sa1.py模塊,只需要在start.py中:

import packA.a1
import packB.b1
import packA.subA.sa1

如果只需要導入比如a1.py某個函數a1_func(),只需要:

from packA.a1 import a1_func()
from packA.subA.sa1 import sa1_func() # 跟上面同理

注意在start.py和在a1.py導入sa1.py是不同的,在a1.py需要:

import subA.sa1
from subA.sa1 import sa1_func()
from subA import sa2

因爲運行a1.pysys.path的相應目錄已經改變,只包含a1.py腳本所在的目錄。而對於python3,在start.py是不能跨越子目錄直接導入sa1.py的,比如from subA import sa1,但是在python2中可以,下面做個簡單的總結:

運行 from packA.subA import sa1 from subA import sa1
start.py OK Py2 OK, Py3 fail (subA not in test/)
a1.py fail (packA not in test/packA/) OK

場景3:導入父目錄下的模塊

如果需要在a1.py中導入父目錄下other.py或者是packB目錄下的b1.py,此時就需要對sys.path作出修改了,因爲運行a1.pysys.path包含a1.py所在的目錄packA,並不包含目錄packB以及父目錄test
修改sys.path需要用到sys以及os模塊,首先需要知道怎麼獲取當前目錄以及父目錄,在a1.py中如下:

current_path=os.path.dirname(__file__) #當前a1.py所在的目錄packA 
parent_path=os.path.dirname(os.path.dirname(__file__)) #當前a1.py所在的父目錄或者說上級目錄test
p_parent_path=os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ##獲取上上級目錄

下面修改sys.path,對於在a1.py中導入父目錄下other.py或者是packB目錄下的b1.py,只需要將parent_path加入到```sys.path``即可,

sys.path.append(parent_path)

然後就可以導入other.py以及b1.py了,完整代碼如下:

import sys
import os
# print(os.getcwd())
# sys.path.append(os.getcwd())
parent_path=os.path.dirname(os.path.dirname(__file__)) # 獲取上級目錄
p_parent_path=os.path.dirname(os.path.dirname(os.path.dirname(__file__))) # 獲取上上級目錄
sys.path.append(parent_path) # 修改sys.path
import other # 導入test下的other
import packB.b1 # 導入b1
import packA.a2 #導入a2
import a2 # 此時sys.path既包含上級目錄test也包含當前目錄packA,所以跟上面一樣

此外順便提到os.getcwd(),注意該函數是獲取當前終端的路徑而不是腳本的路徑,所以爲了避免混淆,建議採用os.path.dirname(__file__)這樣的形式。

要點提煉

  • import最重要的是看sys.path所包含的目錄,當我們運行腳本時sys.path會自動包含運行腳本所在的目錄
  • python3中不能越級導入,如果sys.path只包含test所在目錄,導入sa1.py時,只能import packA.subA.sa1,而不能import subA.sa1,也不能import sa1
  • 接第二點,如果sys.path經過修改後,既包含test所在目錄,也包含packA所在目錄,那麼import packA.subA.sa1import subA.sa1都可以,如果再包含sa1所在目錄,那麼import sa1也可以
  • 第三點可能會造成困惑,一種建議的習慣就是在每個運行的腳本中加入工程的根目錄即test所在目錄到sys.path中,然後所有導入按照import packA.subA.sa1的形式從第一級目錄追溯到要導入的模塊即可,而不是一會import packA.subA.sa1,一會import subA.sa1陷入混淆,只導入當前目錄所在的模塊依然可以import sa1的形式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章