簡單來看,import 機制可以導入我們需要使用的庫,避免代碼重複,使用方便,可謂是編寫 Python 時最常使用寫法,但我們了解 import 嗎?
import 其實有很多容易混淆的概念以及可以實現很多非常有趣的玩法,本篇文章拋磚引玉,聊聊 import
需注意,Python2 與 Python3 的 import 機制有較大差別,主要體現在兩點:
簡單而言,Python3.7 與 Python2.7 在 import 機制上有較大差異,這裡以Python3.7 為基準進行討論。
模塊、常規包與命名空間包「模塊」、「常規包」與「命名空間包」是理解import機制繞不開的概念。
「模塊」:一個以「.py」為後綴的文件就是一個模塊「常規包」:「__init__.py」所在目錄就是一個常規包「命名空間包」:命名空間包是一種虛擬的概念,它由多個子包構成,這些子包可以在任意位置,可以為 zip 中的文件或網絡上的文件,這些子包在概念上是一個整體,這個整體就是一個命名空間包
「常規包」與「命名空間包」的概念在 PEP420 被提出,在 Python3.3 及之後的 Python 版本中實現,此前只有「常規包」這一種包。
import導入系統通過 import 機制,我們可以使用導入模塊內的代碼。
import 是最常用的導入機制,但並不是唯一的方式,importlib 模塊以及內置的__import__() 方法都可以實現模塊的導入。
import 語句做的是兩個操作
1.搜索操作:在相應的路徑中搜索指定名稱的模塊2.綁定操作:將搜索到的結果綁定到當前作用域對應的名稱上(即創建module對象,並初始化)
通過閱讀 Python 官方文檔可知,import 的搜索操作通過 __import__() 方法實現,而綁定操作只有 import 語句才能做到。
當某個模塊初次被 import 時,Python 會在相應的路徑去搜索該模塊,如果模塊存在,則在當前作用域下創建一個 module 對象並進行初始化,如果未找到,則拋出 ModuleNotFoundError。
import 在導入模塊時,會根據 sys.path 中定義的路徑來搜索對應的模塊,sys.path 是一個 list,import 時會先從 sys.path 中下標為 0 的路徑開始搜索,我們可以將需要的路徑添加到 sys.path。
如果我們沒有修改,sys.path 中默認的路徑為:
要修改搜索路徑有3種方式:
動態修改 sys.path,因為 sys.path 為 list,所以我們可以很輕鬆的操作 list 實現搜索路徑的修改。這種方式只會對當前項目臨時生效
修改 PYTHONPATH 環境變量,這種方式會永久生效,而且所有的 Python 項目都會受到影響,因為 Python 程序啟動時會自動去讀取該環境良好的值。
增加 .pth 後綴的文件。在 sys.path 已有的某一個目錄下添加 .pth 後綴的配置文件,該文件的內容就是要添加的搜索路徑,Python 在遍歷已有目錄的過程中,如果遇到 .pth 文件,就會將其中的路徑添加到 sys.path 中。
Python 中定義了多種搜索策略去搜索相應的模塊,而這些策略可以通過 importlib 等提供的各類 hook 進行修改實現一些有趣的效果。
在 Python3.3 之後,所有的模塊導入機制都會通過「sys.meta_path」暴露,不會在有任何隱式導入機制。
In [1]: import sys
In [2]: sys.meta_path
Out[2]:
[_frozen_importlib.BuiltinImporter,
_frozen_importlib.FrozenImporter,
_frozen_importlib_external.PathFinder,
<six._SixMetaPathImporter at 0x10f43e860>]
import形式import 主要有兩種形式
1.import xxx
2.from xxx import xxx
可以看到下面的例子:
In [1]: import os
In [2]: from os.path import join
In [3]: import os.path.join
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-3-26fe2f8f50ea> in <module>
----> 1 import os.path.join
ModuleNotFoundError: No module named 'os.path.join'; 'os.path' is not a package
可以看出,第2行與第3行的區別在於是否使用 from,其背後的規則是:
在導入模塊的過程中,首先會搜索sys.modules中內容,sys.modules是模塊的緩存,其中包括了所有此前導入的模塊。
使用的是 import A, 此時會先檢查sys.moduels中是否存在A,如果存在,則不會再去搜索加載,而是直接從緩存中取來用,如果沒有,則搜索模塊並進行綁定操作,隨後便將其加載到sys.modules中
使用的是 from A import B,此時依舊會先檢查A並進行相同的操作,隨後獲得A的module對象後,從中解析並尋找B然後再填充到A的__dict__結構中,從而實現不用標明來自於A,直接通過B的名稱就可直接使用的B的效果。
相對導入與絕對導入當我們import導入模塊或包時,Python提供兩種導入方式:
1.相對導入 relative import2.絕對導入 absolute import
在 Python2.5 之前,Python 默認使用的相對導入,而 Python2.5 之後,以絕對導入為默認使用的導入方式。
這兩個概念與相對路徑、絕對路徑的概念有些相似,幾個具體的例子:
1.絕對導入格式為:import A.B.C 或者 form A.B import C2.相對導入格式為:from . import B 或 from ..A import B,其中.表示當前模塊,..表示上層模塊(與相對路徑概念類似)
在開發第三方模塊時,通常使用相對導入的形式,這可以有效避免硬編碼帶來的問題,比如在開發過程中,我們修改了某個文件的名稱,此時,使用絕對導入形式的 import 就需要修改相應的名稱。這其實也不是絕對的,當包的結構發生改變時,相對導入就會出現問題,當相對模塊名稱的改變而言,目錄結構的改變出現概率會小一些。
尾import 中的基本概念就簡單介紹到這裡,這些概念中還有很多細節值得學習。