本文首發於知乎
使用python時,常常會涉及到庫的呼叫,這就需要掌握模組的基本知識。本文分為如下幾個部分
- 概念說明
- 模組的簡單呼叫
- 包的匯入
- 特殊的
__init__.py
檔案 - 匯入模組的搜尋路徑
__all__
- 絕對引用與相對引用
- import執行本質
if __name__ == `__main__`
概念說明
這裡釐清python中模組、庫、包之間的概念差異
- 模組(module)其實就是py檔案,裡面定義了一些函式、類、變數等
- 包(package)是多個模組的聚合體形成的資料夾,裡面可以是多個py檔案,也可以巢狀資料夾
- 庫是參考其他程式語言的說法,是指完成一定功能的程式碼集合,在python中的形式就是模組和包
模組的簡單呼叫
比如我們有一個trymodule
的資料夾,裡面有一個first.py
檔案,檔案中的內容如下
a = 1
def myfun(s):
print(s + 1)
複製程式碼
在trymodule
的資料夾下開啟命令列視窗(按住shift單擊滑鼠右鍵,選擇“在此處開啟命令視窗”),輸入python
進入命令列互動模式
>>> import first
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name `a` is not defined
>>> first.a
1
>>> first.myfun(2)
3
複製程式碼
所以說first.py檔案就是一個模組,可以用import
匯入,裡面變數都要用first.
字首來引用,如果想不使用這個字首可以這樣
>>> from first import a
>>> a
1
複製程式碼
其他用法如下
# 重新命名
>>> from first import myfun as addone
>>> addone(4)
5
# 匯入模組中全部變數
>>> from first import *
>>> myfun(2)
3
# 一次匯入多個變數
>>> from first import a, myfun
>>> a
1
複製程式碼
包的匯入
在trymodule資料夾中新建folder1
資料夾,我們想讓folder1
資料夾成為一個包。資料夾裡新建abcd.py
檔案,檔案中內容如下
b = 2
class Myclass:
def __init__(self, name, age):
self.name = name
self.age = age
def get_info(self):
print(`my name is {name} and age is {age}`.format(name = self.name, age = self.age))
複製程式碼
此時在folder1
資料夾中新建一個__init__.py
檔案,否則程式會將這個資料夾當成普通資料夾來處理而不是一個包。這個__init__.py
檔案中可以什麼都不填。
此時檔案結構如下
trymodule
│ first.py
├───folder1
│ │ abcd.py
│ │ __init__.py
複製程式碼
我們還是在trymodule
資料夾下開啟命令列,進入python互動模式
我們來看一下下面幾種匯入方式
>>> import folder1
>>> folder1.abcd.b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module `folder1` has no attribute `abcd`
>>> from folder1 import abcd
>>> bob = abcd.Myclass(name = `Bob`, age = 20)
>>> bob.name
`Bob`
>>> bob.get_info()
my name is Bob and age is 20
>>> from folder1.abcd import b
>>> b
2
>>> import folder1.abcd
>>> abcd.b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name `abcd` is not defined
>>> folder1.abcd.b
2
>>> import folder1.abcd as aa
>>> aa.b
2
複製程式碼
注意:
- 只是匯入包不能隨便使用其中的模組,要匯入到具體模組或者變數的層次
- 資料夾與檔案之間可以用
.
也可以用from import
格式,而檔案與裡面的變數之間只能用from import
格式,即不能import folder1.abcd.b
特殊的__init__.py
檔案
__init__.py
檔案其實是一個特殊的檔案,它相當於名為folder1模組,即如果使用import folder1
則可以呼叫在__init__.py
檔案檔案中定義的變數。
將__init__.py
檔案編寫如下
from folder1.abcd import b
c = 3
複製程式碼
在trymodule資料夾下開啟命令列,進入python互動模式
>>> import folder1
>>> folder1.c
3
>>> folder1.b
2
>>> from folder1 import b
>>> b
2
複製程式碼
對比之前的from folder1.abcd import b
,使用__init__.py
檔案可以將常用的一些變數匯入以方便呼叫。
另外需要注意兩點
__init__.py
檔案編寫時,如果要匯入其他模組中的變數,即使__init__.py
檔案和abcd.py
檔案在同一個資料夾下,也不能from abcd import b
,要從abcd檔案從哪裡來的開始寫,即從包的名稱開始。- folder1資料夾裡的巢狀資料夾內不需要新建
__init__.py
檔案即可像模組一樣呼叫,但是一般還是要新建這個檔案,可以方便地匯入常用變數。
匯入模組的搜尋路徑
用import hello
時,python會搜尋hello.py
檔案,搜尋順序如下
- 首先搜尋內建模組是否有
hello
(所以我們定義的模組名不要和內建模組相同) - 如果內建模組沒有,則看下面這些目錄裡有沒有
>>> import sys
>>> sys.path
[``, `C:\Program Files\Anaconda3\python35.zip`, `C:\Program Files\Anaconda3\DLLs`, `C:\Program Files\Anaconda3\lib`, `C:\Program Files\Anaconda3`, `C:\Program Files\Anaconda3\lib\site-packages`, `C:\Program Files\Anaconda3\lib\site-packages\Sphinx-1.4.6-py3.5.egg`, `C:\Program Files\Anaconda3\lib\site-packages\snownlp-0.12.3-py3.5.egg`, `C:\Program Files\Anaconda3\lib\site-packages\win32`, `C:\Program Files\Anaconda3\lib\site-packages\win32\lib`, `C:\Program Files\Anaconda3\lib\site-packages\Pythonwin`, `C:\Program Files\Anaconda3\lib\site-packages\setuptools-27.2.0-py3.5.egg`]
複製程式碼
其中第一個``
表示當前的工作路徑,我們可以看出安裝的第三方包所在路徑(`C:\Program Files\Anaconda3\lib\site-packages`
)也在這個列表之中,所以無論工作路徑在哪裡,都能搜尋到這些包。
如果想新增搜尋路徑,可以參考這篇文章
__all__
首先要明確,import *
的方式無法匯入以下劃線開頭的變數名
__init__.py
檔案內容更改如下
from folder1.abcd import b
c = 3
_e = 4
複製程式碼
python互動模式下
>>> from folder1 import *
>>> c
3
>>> _e
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name `_e` is not defined
複製程式碼
而如果指定匯入是可以的
>>> from folder1 import c
>>> c
3
>>> from folder1 import _e
>>> _e
4
複製程式碼
如果定義了__all__
,則import *
就可以匯入下劃線開頭的變數
__init__.py
檔案內容更改如下
from folder1.abcd import b
__all__ = [`c`, `_e`]
c = b + 1
_e = 4
複製程式碼
python互動模式下
>>> from folder1 import *
>>> b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name `b` is not defined
>>> c
3
>>> _e
4
複製程式碼
可見import *
只會匯入__all__
中指定的變數,無論是否以下劃線開頭。這樣限制可以防止import *
命令匯入太多變數汙染名稱空間,過濾掉一些中間變數如b
絕對引用與相對引用
python中的import
分為絕對引用和相對引用兩種。它們之間的差異在於,引用模組時 定位被引用模組位置 的方式不同
- 絕對引用是明確指定最高階檔案(夾),檔案之間用
.
連線,依次下來達到待引用模組。我們上面的所有用法都屬於絕對引用。 - 而相對引用是 指定待引用模組與當前檔案的相對位置,
.
表示上一級檔案
在這樣的檔案結構下
trymodule
│ first.py
├───folder1
│ │ abcd.py
│ │ __init__.py
複製程式碼
編寫__init__.py
檔案,其中要引用abcd.py
檔案中的變數
- 絕對引用是
from folder1.abcd import b
- 相對引用是
from .abcd import b
相對引用中,.
是指父檔案(也有from . import xxx
的用法),.xxx
是指同一層檔案,..xxx
則是與父資料夾同級的xxx
檔案(多一個.
表示多往上一層)
一般用哪個呢?
python3之後官方推薦用絕對引用的方式,只有當模組中檔案關係非常複雜時相對引用才會有優勢。
import執行本質
使用import
語句,要明確兩件事
(1)執行匯入模組命令時,會首先檢查待匯入的模組是否在當前已有模組之中,如果有則跳過import
。因此模組之間相互引用不會導致無限迴圈。
檢視當前已匯入模組使用下面方法
import sys
sys.modules
複製程式碼
得到結果是一個字典,鍵是模組名,值是檔案所在路徑
(2)import
語句與檔案執行
在這樣的檔案結構下
trymodule
│ first.py
├───folder1
│ │ abcd.py
│ │ __init__.py
複製程式碼
folder1是一個package,abcd是一個module
import folder1
只是匯入package,相當於執行__init__.py
檔案from folder import abcd
則執行了__init__.py
檔案檔案與abcd.py
檔案from folder1.abcd import b
其實也執行了__init__.py
檔案檔案與abcd.py
檔案
(要知道執行了什麼,可以在這些檔案之中新增print
語句,看是否列印出結果)
知道這個執行原理,可以更好理解前面得到的一些結論
- 首先是在
__init__.py
檔案中什麼都沒有的情況下,import folder1
無法呼叫abcd
模組中的變數,是因為相當與執行了一個空檔案,沒有將整個包匯入工作空間 abcd
模組中定義了print
語句後,import
兩次,只有第一次會print
出值,說明第二次檢查出模組已在匯入之列,忽略了這條import
命令
更多執行細節可以參考這篇文章
if __name__ == `__main__`
我們經常會在別人的程式碼中發現if __name__ == `__main__`
,為了理解它的作用,我們來看下面的例子
在folder1資料夾下新建new.py檔案,裡面內容為
print(__name__)
複製程式碼
在folder1資料夾下開啟命令列,輸入
python new.py
複製程式碼
返回結果是__main__
在trymodule資料夾下開啟命令列,進入python互動模式
>>> from folder1 import new
folder1.new
>>> print(__name__)
__main__
>>> print(new.__name__)
folder1.new
複製程式碼
上面測試結果說明直接執行檔案和import
檔案是有差異的,差異在於二者的__name__
變數不同。__name__
變數是一個特殊的變數,每個py檔案執行時都會對應一個__name__
變數,即使在互動模式下也可以檢視這個變數值。
所以if __name__ == `__main__`
的作用就很好理解了,即import
時不執行下面的程式碼,只有在直接執行這個檔案時才執行之後的程式碼。這算是一種約定俗成的寫法,如果不怕檔案被import
,可以不用這個。
歡迎關注我的知乎專欄
專欄主頁:python程式設計
專欄目錄:目錄
版本說明:軟體及包版本說明