標籤:install 機器 區別 pre rac /usr href 不能 case
模組與包
在瞭解 import 之前,有兩個概念必須提一下:
- 模組: 一個
.py 檔案就是一個模組(module)
- 包:
__init__.py 檔案所在目錄就是包(package)
當然,這隻是極簡版的概念。實際上包是一種特殊的模組,而任何定義了 __path__ 屬性的模組都被當做包。只不過,咱們日常使用中並不需要知道這些。
兩種形式的 import
import 有兩種形式:
import ...
from ... import ...
兩者有著很細微的區別,先看幾行代碼。
from string import ascii_lowercaseimport stringimport string.ascii_lowercase
運行後發現最後一行代碼報錯:ImportError: No module named ascii_lowercase,意思是:“找不到叫 ascii_lowercase 的模組”。第 1 行和第 3 行的區別只在於有沒有 from,翻翻文法定義發現有這樣的規則:
import ... 後面只能是模組或包
from ... import ... 中,from 後面只能是模組或包,import 後面可以是任何變數
可以簡單的記成:第一個空只能填模組或包,第二個空填啥都行。
import 的搜尋路徑
提問,下面這幾行代碼的輸出結果是多少?
import stringprint(string.ascii_lowercase)
是小寫字母嗎?那可不一定,如果分類樹是這樣的:
./├── foo.py└── string.py
foo.py 所在目錄有叫 string.py 的檔案,結果就不確定了。因為你不知道 import string 到底是 import 了 ./string.py 還是標準庫的 string。為了回答這個問題,我們得瞭解一下 import 是怎麼找到模組的,這個過程比較簡單,只有兩個步驟:
- 搜尋「內建模組」(built-in module)
- 搜尋
sys.path 中的路徑
而 sys.path 在初始化時,又會按照順序添加以下路徑:
foo.py 所在目錄(如果是軟連結,那麼是真正的 foo.py 所在目錄)或目前的目錄;
- 環境變數
PYTHONPATH中列出的目錄(類似環境變數 PATH,由使用者定義,預設為空白);
site 模組被 import 時添加的路徑1(site 會在運行時被自動 import)。
import site 所添加的路徑一般是 XXX/site-packages(Ubuntu 上是 XXX/dist-packages),比如在我的機器上是 /usr/local/lib/python2.7/site-packages。同時,通過 pip 安裝的包也是儲存在這個目錄下的。如果懶得記 sys.path的初始化過程,可以簡單的認為 import 的尋找順序是:
- 內建模組
.py 檔案所在目錄
pip 或 easy_install 安裝的包
相對 import 與 絕對 import相對 import
當項目規模變大,代碼複雜度上升的時候,我們通常會把一個一個的 .py 檔案組織成一個包,讓項目結構更加清晰。這時候 import 又會出現一些問題,比如:一個典型包的目錄結構是這樣的:
string/├── __init__.py├── find.py└── foo.py
如果 string/foo.py 的代碼如下:
# string/foo.pyfrom string import findprint(find)
那麼 python string/foo.py 的運行結果會是下面的哪一個呢?
<module ‘string.find‘ from ‘string/find.py‘>
<function find at 0x123456789>
按我們前面講的各種規則來推導,因為 foo.py 所在目錄 string/ 沒有 string 模組(即 string.py),所以 import 的是標準庫的 string,答案是後者。不過,如果你把 foo 當成 string 包中的模組運行,即 python -m string.foo,會發現運行結果是前者。同樣的語句,卻有著兩種不同的語義,這無疑加重了咱們的心智負擔,總不能每次咱們調試包裡的模組時,都去檢查一下執行的命令是 python string/foo.py 還是 python -m string.foo 吧?
相對 import 就是專為解決「包內匯入」(intra-package import)而出現的。它的使用也很簡單,from 的後面跟個 . 就行:
# from string/ import find.pyfrom . import find# from string/find.py import *from .find import *
我們再看個複雜點的例子,有個包的目錄結構長這樣:
one/├── __init__.py├── foo.py└── two/ ├── __init__.py ├── bar.py └── three/ ├── __init__.py ├── dull.py └── run.py
from . import dullfrom .. import barfrom ... import fooprint(‘Go, go, go!‘)
改成
from .dull import *from ..bar import *from ...foo import *print(‘Go, go, go!‘)
結果是一樣的。
那麼 python string/foo.py 和 python -m string.foo 的運行結果又是怎樣呢?運行一下發現,兩者的輸出分別是:
Traceback (most recent call last): File "string/foo.py", line 1, in <module> from . import findValueError: Attempted relative import in non-package
<module ‘string.find‘ from ‘string/find.py‘>
原因在於 python string/foo.py 把 foo.py 當成一個單獨的指令碼來運行,認為 foo.py 不屬於任何包,所以此時相對 import 就會報錯。也就是說,無論命令列是怎麼樣的,運行時 import 的語義都統一了,不會再出現運行結果不一致的情況。
Python的import機制