Python模組(module)

ThankCAT 發表於 2023-01-04
Python

在此之前,我們執行一些簡短的Python程式碼,都是通過Python直譯器(python或ipython)進行的,如果我們退出直譯器再重新執行直譯器後,上次敲進去的變數、函式等都不見了,沒有儲存下來。為了儲存我們曾經寫過的程式碼,就是要寫成.py檔案,稱為指令碼

如果你這個指令碼想要使用其它指令碼里面的變數、函式和類也是可以的,在你這個指令碼里面用import來匯入要引用的指令碼,而那個被引用的指令碼就稱作模組(module)

Python模組

簡單來說,一個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中匯入m1m2這兩個模組:

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點)都要涉及到。

猿人學banner宣傳圖

我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。

***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***