使用者:Xyy23330121/Python/模組
我們之前接觸過 import
語句以導入模塊。本章將詳細講解 import
語句的工作方式,如何編寫模塊,以及如何安裝第三方模塊。
任何 .py
的文件都是一個簡單的模塊。為簡單起見,我們新建一個文件夾,在其中放兩個文件:
folder/
MyModule.py
test.py
其中 MyModule.py
的內容為:
#!/usr/bin/python
# -*- coding: utf-8 -*-
var = "变量"
def f(): print("函数")
class C:
def __str__(self): return "类"
而 test.py
的內容為:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import MyModule
print(MyModule.var)
MyModule.f()
obj = MyModule.C()
print(obj)
運行 test.py
,輸出為:
变量
函数
类
可見,我們可以正常導入之前已經寫過的文件當中的變量、函數以及類。而 MyModule.py
就已經是一個模塊了。
模塊同類、函數、一般的變量一樣,它也是一個對象。對於該對象的具體操作暫不贅述,這裡僅作為拓展。
如果一個文件夾具有 __init__.py
文件,則該文件夾會被視為一個模塊(包)。
就像維基學院的頁面可以一個頁面有幾個分頁面一樣,模塊的包也可以一個包包含幾個子包。父包和子包在文件系統中的體現,就是父包是帶有 __init__.py
的父文件夾,而子包是帶有 __init__.py
的、父包文件夾的子文件夾。
這裡我們執行以下步驟以創建一個簡單的包。
- 把上面「簡單模塊」章節中的
MyModule.py
重命名為__init__.py
- 新建一個
MyModule
文件夾,把__init__.py
移動進去,形成這樣的結構:
folder/
test.py
MyModule/
__init__.py
我們運行 test.py
,輸出和上面的「簡單模塊」章節中的內容是一致的。
我們依舊使用上面的示例。如果我們需要導入tkinter
中 ttk
子模塊時,應當這樣導入:
import tkinter.ttk
但是,如果用上面的方法進行導入。我們在使用包的內容時,則需要用以下方式,寫明包的全稱來調用包中的東西,比如:
tkinter.ttk.Label(text = "test")
我們有兩種解決方法。第一種是 import ... as ...
,第二種是利用 from ... import ...
。
類似 from math import trunc
可以讓之後調用 math.trunc
時無需標註包名,直接用 trunc
即可。相對調用也可以用於省略父包的名稱。比如:
from tkinter import ttk
ttk.Label(text = "test") #相比上面的调用,省略了"tkinter."
這對於前綴過長時十分好用。
對於包中的內容而言,支持一種稱作「相對導入」的方式。我們從文件樹開始。
folder/
__init__.py
test.py
MyModule/
__init__.py
test2.py
test3.py
SubModule/
__init__.py
test4.py
MyModule2/
__init__.py
test5.py
此時,在 test2.py
中支持以下操作:
from . import test3 #导入test3.py
from .. import test #导入test.py
from .SubModule import test4 #导入test4.py
from ..MyModule2 import test5 #导入test5.py
相對導入是關乎模塊名稱的。我們在導入模塊後,可以用 __name__
屬性來訪問模塊的名稱,比如:
import tkinter.ttk as ttk
print(ttk.__name__) #输出:tkinter.ttk
而直接被執行的(而非被導入)的文件總會以'__main__'
為名稱。比如我們直接執行以下代碼:
print(__name__) #输出:__main__
相對導入和文件的 __name__
屬性有關。在導入時,會按照被導入文件的 __name__
屬性決定相對導入的對象。之所以上面在 test2.py
中的操作可以成功,是因為我們先執行了:
import folder.MyModule.test2
此時,test2.py
的 __name__
屬性為 folder.MyModule.test2
。在導入 test2.py
中代碼時,test2.py
中的內容會按照 test2.py
的 __name__
屬性,被解釋為:
from . import test3 #"." 被替换为 "folder.MyModule."
from .. import test #".." 被替换为 "folder."
from .SubModule import test4 #"." 被替换为 "folder.MyModule."
from ..MyModule2 import test5 #".." 被替换为 "folder."
若直接運行 test2.py
則 test2.py
的 __name__
屬性變為了 __main__
,此時就不適用於相對導入。使用了相對導入的文件不應該被直接執行。
特別的,對於 folder/__init__.py
,直接用:
from MyModule import test2
也是相對導入。
由於上面的特性,我們可以通過 __name__
來判斷文件是否被直接執行。比如:
if __name__ == "__main__":
print("被直接执行")
else:
print("不被直接执行")
此時,可以通過不同的運行狀況,來使用對應的策略。比如:
def times(a,b):
return a*b
if __name__ == "__main__":
print(
times(
float(input("输入两个数字,输出乘法结果。\n第一个数字:")),
float(input("第二个数字:"))
))
對於僅有單個 .py 文件組成的模塊,直接執行模塊就是直接執行該 .py 文件。而對於包而言,直接執行模塊時,會執行其目錄中的 __main__.py
文件。
讀者既可以打開包的位置並手動執行其中的 __main__.py
,也可以在比如 cmd.exe
中使用以下指令,直接執行已被安裝的包:
python -m PackageName
如果一個包中沒有 __main__.py
,則那個包就不應被直接執行。
我們也可以從模塊中導入 __main__ 的內容。比如:
folder/
importmain.py
test.py
其中 importmain.py
的內容為:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import __main__
def prtvar: print(__main__.var)
而 test.py
的內容為:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import importmain
var = "__main__中的变量"
importmain.prtvar()
運行 test.py
,輸出為:
__main__中的变量
可以看出,我們在模塊中反過來調用了當時直接運行的文件中的變量。每個直接運行的文件,在運行中,可以被視為以 __main__
為名稱的模塊。
在模塊中,一些特殊的變量在運行時可能有特殊的效果。如果模塊是包,這些變量應該寫在 __init__.py
里。
在 from Module import *
時,導入的內容名稱列表。比如在 json
模塊中,有:
__all__ = [
'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
]
如果我們使用:
from json import *
相當於:
from json import dump, dumps, load, loads, JSONDecoder, JSONDecodeError, JSONEncoder
如果不設置此項,則默認會導入所有對象(如果是包,還會導入所有子模塊),這會導致運行時間延長,並可能導致一些其它的副作用。
還有一些其它參數可以使用。這些變量在導入時作用不大,但在一些情況下可能有幫助。
參數名 | 類型 | 解釋 |
---|---|---|
__version__ | 字符串 | 表示模塊版本的字符串 |
__author__ | 字符串 | 表示模塊作者,經常還包含作者的電子郵箱。比如'Sample Name <Sample@example.com>'
|
上面 Python 文檔中所示屬性會在導入時自動寫入,並可以被調用。
如果每創建一個新項目,都要把之前寫的模塊複製到項目文件夾下,無疑是很麻煩的。我們之前導入 keyword
模塊時,也沒有在項目文件夾中創建 keyword.py
。可見,導入模塊不需要把模塊文件放在項目文件夾下。
對於一個語句 import Module
,Python 會按先後順序查詢以下內容。
在運行 Python 時,會先查詢已有的模塊緩存 sys.modules
。該緩存保存了已經調用的所有模塊的信息。如果新調用的模塊恰好在這些信息內,則會直接從該信息調用新模塊。
比如,如果之前導入過 MyModule.SubModule
,則 MyModule
和 MyModule.SubModule
都會被寫入緩存中。
如果在緩存中找不到,則會使用查找器來進一步查找。讀者無需詳細了解查找器的內容,簡單來講,查找器會查找:
- 內置模塊
比如keyword
、math
等。 - 文件路徑
當前運行的文件,其所在文件夾中是否有對應模塊。 - 安裝的模塊路徑
已經安裝的模塊所在的路徑。
以Windows系統為例,默認是安裝在 %AppData%\Python\Python312\site-packages
除 Python 的內置模塊、讀者自己撰寫的模塊之外。Python 社區還提供了許多第三方模塊。
我們一般使用 pip 來安裝第三方模塊。pip 是 Python 附帶的包管理器,我們可以在 cmd.exe 或其它類似的命令行程序中,輸入以下的內容:
pip install numpy
並按回車,以安裝第三方的 numpy 模塊。
在安裝之前,讀者應當先了解到第三方庫的具體內容。比如我們希望能找到更快地、進行數字計算的方式,具體有以下幾類方法:
- 搜尋引擎:在搜尋引擎上搜索形如「Python 高效數字計算」的內容。
如果使用谷歌等外國搜尋引擎,還可以搜索這些關鍵詞的英文版本。 - 第三方社區:在 Stack Overflow 或 zhihu 等問答網站上,搜索類似的關鍵詞。
- Python官方的指引:前往 https://www.python.org/ 找到 Use Python for… 章節,並查看其內容。
在搜尋時,要重點關注第三方庫的文檔。文檔會對安裝和使用方法有較為詳細的介紹。有的文檔甚至附帶使用教程。
由於中國大陸的網絡環境過於「良好」,讀者可能出現下載超時等情況導致安裝失敗。為此,有時需要添加額外的安裝參數。
中國大陸內部有許多組織會在自己的伺服器上「託管」一些常用的 Python 包。以清華大學為例,我們可以嘗試使用以下的方式,從清華大學的伺服器下載並安裝 numpy:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy
如果讀者可以通過代理伺服器來訪問網際網路,則可以使用以下方法:
pip install --proxy 127.0.0.1:1080 numpy
這裡的代理伺服器設置為 127.0.0.1:1080
。讀者應當隨著自己的代理伺服器情況自行調整設置。
默認情況下,下載伺服器在 15 秒內沒有響應就會超時。我們可以添加一些參數來修改超時時間:
pip install --timeout 99999 numpy
這裡設置為 99999 秒。
以上的參數可以結合起來使用。比如:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --timeout 99999 numpy
對於其它的疑難雜症,我們可以輸入以下內容,查看使用幫助:
pip help
第三方模塊的文檔許多都是英文,如果讀者有足夠的英文功底,或者能使用翻譯軟體,可以嘗試直接閱讀第三方模塊的官方文檔。如果使用翻譯軟體,最好是對照著中英文來閱讀,因為翻譯可能不準確。
如果沒有足夠的英文功底,讀者就需要到網站上,找到類似本資料的中文學習資料。此時讀者已經經過「搜尋第三方庫」找到了第三方庫的名稱,可以直接上搜尋引擎搜索比如「Python numpy」的內容,社區會為讀者提供幫助的。
如果有照著教程或文檔使用還不了解的內容,讀者可以選擇去 Stack Overflow 等問答網站上求助,也可以自己做一些測試。之前章節中 這個頁面 就是本資料作者進行的測試。