用戶: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 的、父包文件夾的子文件夾。

這裏我們執行以下步驟以創建一個簡單的包。

  1. 把上面「簡單模塊」章節中的 MyModule.py 重命名為 __init__.py
  2. 新建一個 MyModule 文件夾,把 __init__.py 移動進去,形成這樣的結構:
folder/
    test.py
    MyModule/
        __init__.py

我們運行 test.py,輸出和上面的「簡單模塊」章節中的內容是一致的。

import 語句與包[編輯 | 編輯原始碼]

我們依舊使用上面的示例。如果我們需要導入tkinterttk 子模塊時,應當這樣導入:

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.pytest2.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 ,則 MyModuleMyModule.SubModule 都會被寫入緩存中。

使用查找器[編輯 | 編輯原始碼]

如果在緩存中找不到,則會使用查找器來進一步查找。讀者無需詳細了解查找器的內容,簡單來講,查找器會查找:

  1. 內置模塊
    比如 keywordmath等。
  2. 文件路徑
    當前運行的文件,其所在文件夾中是否有對應模塊。
  3. 安裝的模塊路徑
    已經安裝的模塊所在的路徑。

已經安裝的模塊所在路徑[編輯 | 編輯原始碼]

以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 對象支持的一切方法和屬性。