python模組詳解

dwzb發表於2019-03-04

本文首發於知乎

使用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程式設計

專欄目錄:目錄

版本說明:軟體及包版本說明

相關文章