在此之前,我們執行一些簡短的Python程式碼,都是通過Python直譯器(python或ipython)進行的,如果我們退出直譯器再重新執行直譯器後,上次敲進去的變數、函式等都不見了,沒有儲存下來。為了儲存我們曾經寫過的程式碼,就是要寫成.py
檔案,稱為指令碼
。
如果你這個指令碼想要使用其它指令碼里面的變數、函式和類也是可以的,在你這個指令碼里面用import
來匯入要引用的指令碼,而那個被引用的指令碼就稱作模組(module)
。
簡單來說,一個Python原始碼檔案(*.py)就是一個模組。
我們的第一個Python模組
接下來,我們用文字編輯器(比如,前面介紹的VS Code)來建立一個名為 my_first_module.py
的檔案作為我們編寫的第一個模組:
#!/usr/bin/env python3
# coding:utf-8
# Author: veelion
# file name: my_first_module.py
'''My First Module'''
MY_NAME = 'My_First_Module'
def my_print(a):
print(MY_NAME, ' print', a)
def my_add(a, b):
return a+b
我們的第一個Python模組裡面有一個全域性變數:MY_NAME
,兩個函式:my_print()
和my_add()
。接著我們在這個檔案所在目錄
執行Python直譯器ipython:
In [1]: import my_first_module
In [2]: my_first_module?
Type: module
String form: <module 'my_first_module' from '/home/veelion/p2/tutorial/md_Python/codes/my_first_module.py'>
File: ~/p2/tutorial/md_Python/codes/my_first_module.py
Docstring: My First Module
In [3]: my_first_module.MY_NAME
Out[3]: 'My_First_Module'
In [4]: my_first_module.my_add(2, 3)
Out[4]: 5
In [5]: my_first_module.my_print('猿人學')
My_First_Module print 猿人學
匯入模組用import
,模組名稱就是檔名my_first_module.py
去掉檔案字尾.py
後的名字。從上面ipython的使用中,我們可以看到模組中的函式、變數都是可以被拿來用的。
注意: Python模組的檔名只能是字母、數字和下劃線,不能有-,+
等其它符號,否則匯入會報錯,原因很簡單,比如-
符號會和Python裡面的減號混淆。
把上面的模組重新命名為my-first-module.py
,再import
匯入一下看看:
In [6]: import my-first-module
File "<ipython-input-6-a8306ca40c5e>", line 1
import my-first-module
^
SyntaxError: invalid syntax
Python模組的二三事
(1)模組可以包含可執行的全域性語句。這些語句應該是用於初始化該模組,它們只在第一次被import
時執行。我們來舉個例子,建立兩個只包含一句print
的模組:
# m1.py
print('m1 is imported')
# m2.py
import m1
print('m2 is imported')
在main.py
中匯入m1
和m2
這兩個模組:
import m1
import m2
import m1
print('I am main.py')
這裡m1
被顯性匯入兩次,還有一次被m2
陰性匯入,一共匯入三次,那麼是不是m1 is imported
會被列印3次呢?我們執行這個指令碼試試看: python main.py
。猜猜執行結果是怎樣的?
m1 is imported
m2 is imported
I am main.py
結果是隻被列印了一次。這就是只在第一次被import
時執行的意思。再試試把main.py
中的兩個import m1
都去掉,只import m2
會是什麼結果?
(2)每個模組都都它自己私有的符號表,它被當做全域性符號表被該模組中所有函式使用,也就是說,每個模組都有自己的名字空間。因此,模組裡面可以盡情(如有必要)使用全域性變數,而不用擔心它們與模組使用者的全域性變數衝突。使用者使用模組中的全域性變數也很簡單:modname.itemname
。
比如,my_first_module
模組中的MY_NAME
使用時就是my_first_module.MY_NAME
,而在你自己的指令碼里面同樣可以命名MY_NAME
的全域性變數,而不會和my_first_module
裡面的衝突。
(3)模組可以import
其它模組。模組匯入語句import
不一定要在指令碼的最開始,可以在程式碼其它位置需要時匯入。當然,在最開始匯入是最清晰、規範的做法。
import 模組的各種方式
我們使用import
的方式很多,前面那種 import module_name
的方式是最常用的,也是程式碼規範推崇的用法。從語法上講還有其它方式:
(1)用from匯入部分:
In [1]: from my_first_module import my_add
In [2]: my_add(1,3)
Out[2]: 4
In [3]: my_print('hi')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-b42bb20df9e4> in <module>
----> 1 my_print('hi')
NameError: name 'my_print' is not defined
In [4]: my_first_module.my_add(1,2)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-4-df5ce230b443> in <module>
----> 1 my_first_module.my_add(1,2)
NameError: name 'my_first_module' is not defined
通過from modname import xxx
的方式,我們只匯入了my_add
,呼叫my_print
就會出錯。並且,my_first_module
模組名稱也是未定義的,即沒有被匯入。
(2)用from匯入部分並重新命名
跟(1)一樣,只不過把匯入的名稱起了別名而已,使用時用別名:
from my_first_module import my_add as myadd
(3)用from匯入全部
In [1]: from my_first_module import *
In [2]: my_add(1,2)
Out[2]: 3
In [3]: my_print('猿人學')
My_First_Module print 猿人學
In [4]: MY_NAME
Out[4]: 'My_First_Module'
這種方式看似簡單,寫程式碼時省去了模組名稱my_first_module
這個字首。但是,這個省略帶來很大隱患,會限制我們自己命名。如果我們自己命名和模組裡面的名稱一樣,就會覆蓋模組裡面的名字。
這種import的方式是程式碼規範嚴禁杜絕的
(4)重新命名模組
如果模組名稱很長,我們可以給它起個短一點的別名,這樣寫程式碼會簡單些:
In [1]: import my_first_module as mfm
In [2]: mfm.my_add(1,2)
Out[2]: 3
In [3]: mfm.my_print('猿人學')
My_First_Module print 猿人學
In [4]: mfm.MY_NAME
Out[4]: 'My_First_Module'
這個和import my_first_module
實際上一樣,只是使用的名稱變為mfm
。模組的別名可以任意起,只要和其它名稱區分開來就好。
(5)模組重新載入
我們寫完一個模組,可能要通過Python直譯器(如ipython)進行驗證一下,於是執行Python直譯器,import模組,發現模組的某個函式有錯誤,就在編輯器修改了該函式並儲存該模組檔案。繼續在剛才開啟的直譯器裡面驗證那個有錯誤的函式,發現剛才的修改沒生效,竟然沒有生效!!!
為什麼呢?為了效率,每個直譯器匯入的模組只匯入一次。因此,如果你中途修改了模組,就要出直譯器重新進入並重新匯入模組才能使修改生效。如果不退出直譯器而重新匯入模組,不管你執行多少次import modname
都是無效的,因為直譯器一看這個模組已經匯入過了,就不費勁再匯入一次了。直譯器懶,你就不能懶。
或者,可以不重新啟動直譯器而使用importlib.reload()
重新匯入。
把Python模組當做指令碼執行
任何Python檔案都可以這樣來執行:
python3 file.py
一個檔案的Python模組當然也可以這樣執行。一個Python檔案,前面是函式的定義,定義完要執行,我們就要寫呼叫語句,最初你相到的可能是這樣的:
# Author: veelion
# file: mylib.py
def add(a, b):
return a+b
print(add(2, 3))
通過python mylib.py
執行一下,就可以得到執行結果。
目前看起來一切正常,你看看有沒有問題?
回頭看看上面模組二三事的第(1)條,如果這個檔案當做模組被其它檔案import
時,是不是也會執行列印語句?這條列印語句往往是我們為了驗證add()
函式而進行的,屬於測試階段的程式碼,而交付給他人作為模組使用時,它是不需要的。那麼,該怎麼辦?
通過__name__
屬性就可以來限制print(add(2, 3))
語句的執行。檔案作為指令碼執行時,它的__name__
屬性是__main__
,而作為模組被import時,它的__name__
屬性是模組的名稱。
先看看模組被import時的__name__
:
In [24]: import mylib
5
In [25]: mylib.__name__
Out[25]: 'mylib'
我們可以看到,import mylib
後列印出了5
,也就是執行了print(add(2, 3))
語句。
然後,我們修改mylib.py
檔案,把測試語句修改一下:
# Author: veelion
# file: mylib.py
def add(a, b):
return a+b
if __name__ == '__main__':
print(add(2, 3))
再次在ipython直譯器裡面匯入該模組時就不會列印出5
,也就是那句print不再執行。
而在命令列下執行python3 mylib.py
這個指令碼就會執行那句print語句,因為這種執行方式下,模組的__name__
為__main__
。
這些用__name__ == '__main__'
條件判斷的程式碼通常是該模組的測試程式碼,或者是如何使用該模組的示例程式碼。
Python模組總結
(1)一個Python檔案就是一個模組;
(2)一個模組可以import其它模組;
(3)在Python直譯器執行中,一個模組只可以被import一次,除非使用importlib.reload();
(4)模組中的可執行語句(非函式、類的定義)僅在該模組被import時執行一次。
(5)import模組的方式有多種,要使用最規範的方式。
Python模組練習
編寫你自己的一個Python模組,把本節講到的知識點(總結的5點)都要涉及到。
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***