使用者: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
就已經是一個模塊了。
拓展:module 對象[編輯 | 編輯原始碼]
模塊同類、函數、一般的變量一樣,它也是一個對象。對於該對象的具體操作暫不贅述,這裡僅作為拓展。
簡單包[編輯 | 編輯原始碼]
如果一個文件夾具有 __init__.py
文件,則該文件夾會被視為一個模塊(包)。
就像維基學院的頁面可以一個頁面有幾個分頁面一樣,模塊的包也可以一個包包含幾個子包。父包和子包在文件系統中的體現,就是父包是帶有 __init__.py
的父文件夾,而子包是帶有 __init__.py
的、父包文件夾的子文件夾。
這裡我們執行以下步驟以創建一個簡單的包。
- 把上面「簡單模塊」章節中的
MyModule.py
重命名為__init__.py
- 新建一個
MyModule
文件夾,把__init__.py
移動進去,形成這樣的結構:
folder/
test.py
MyModule/
__init__.py
我們運行 test.py
,輸出和上面的「簡單模塊」章節中的內容是一致的。
import 語句與包[編輯 | 編輯原始碼]
我們依舊使用上面的示例。如果我們需要導入tkinter
中 ttk
子模塊時,應當這樣導入:
import tkinter.ttk
但是,如果用上面的方法進行導入。我們在使用包的內容時,則需要用以下方式,寫明包的全稱來調用包中的東西,比如:
tkinter.ttk.Label(text = "test")
我們有兩種解決方法。第一種是 import ... as ...
,第二種是利用 from ... import ...
。
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
也是相對導入。
__main__[編輯 | 編輯原始碼]
由於上面的特性,我們可以通過 __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__[編輯 | 編輯原始碼]
我們也可以從模塊中導入 __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
里。
__all__[編輯 | 編輯原始碼]
在 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 文檔中所示屬性會在導入時自動寫入,並可以被調用。
import 語句查找模塊的順序[編輯 | 編輯原始碼]
如果每創建一個新項目,都要把之前寫的模塊複製到項目文件夾下,無疑是很麻煩的。我們之前導入 keyword
模塊時,也沒有在項目文件夾中創建 keyword.py
。可見,導入模塊不需要把模塊文件放在項目文件夾下。
對於一個語句 import Module
,Python 會按先後順序查詢以下內容。
模塊緩存[編輯 | 編輯原始碼]
在運行 Python 時,會先查詢已有的模塊緩存 sys.modules
。該緩存保存了已經調用的所有模塊的信息。如果新調用的模塊恰好在這些信息內,則會直接從該信息調用新模塊。
比如,如果之前導入過 MyModule.SubModule
,則 MyModule
和 MyModule.SubModule
都會被寫入緩存中。
使用查找器[編輯 | 編輯原始碼]
如果在緩存中找不到,則會使用查找器來進一步查找。讀者無需詳細了解查找器的內容,簡單來講,查找器會查找:
- 內置模塊
比如keyword
、math
等。 - 文件路徑
當前運行的文件,其所在文件夾中是否有對應模塊。 - 安裝的模塊路徑
已經安裝的模塊所在的路徑。
已經安裝的模塊所在路徑[編輯 | 編輯原始碼]
以Windows系統為例,默認是安裝在 %AppData%\Python\Python312\site-packages
dir 函數[編輯 | 編輯原始碼]
內置函數 dir
會輸出模塊、類、函數等對象中,已有的屬性、方法和函數列表。比如:
dir(float)
會輸出:
['__abs__', '__add__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getformat__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__round__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', 'as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real']
這包含了 float 對象支持的一切方法和屬性。