6. 模塊?
退出 Python 解釋器后,再次進(jìn)入時(shí),之前在 Python 解釋器中定義的函數和變量就丟失了。因此,編寫(xiě)較長(cháng)程序時(shí),建議用文本編輯器代替解釋器,執行文件中的輸入內容,這就是編寫(xiě) 腳本 。隨著(zhù)程序越來(lái)越長(cháng),為了方便維護,最好把腳本拆分成多個(gè)文件。編寫(xiě)腳本還一個(gè)好處,不同程序調用同一個(gè)函數時(shí),不用每次把函數復制到各個(gè)程序。
為實(shí)現這些需求,Python 把各種定義存入一個(gè)文件,在腳本或解釋器的交互式實(shí)例中使用。這個(gè)文件就是 模塊 ;模塊中的定義可以 導入 到其他模塊或 主 模塊(在頂層和計算器模式下,執行腳本中可訪(fǎng)問(wèn)的變量集)。
模塊是包含 Python 定義和語(yǔ)句的文件。其文件名是模塊名加后綴名 .py
。在模塊內部,通過(guò)全局變量 __name__
可以獲取模塊名(即字符串)。例如,用文本編輯器在當前目錄下創(chuàng )建 fibo.py
文件,輸入以下內容:
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
現在,進(jìn)入 Python 解釋器,用以下命令導入該模塊:
>>> import fibo
這項操作不直接把 fibo
函數定義的名稱(chēng)導入到當前符號表,只導入模塊名 fibo
。要使用模塊名訪(fǎng)問(wèn)函數:
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果經(jīng)常使用某個(gè)函數,可以把它賦值給局部變量:
>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
6.1. 模塊詳解?
模塊包含可執行語(yǔ)句及函數定義。這些語(yǔ)句用于初始化模塊,且僅在 import 語(yǔ)句 第一次 遇到模塊名時(shí)執行。1 (文件作為腳本運行時(shí),也會(huì )執行這些語(yǔ)句。)
模塊有自己的私有符號表,用作模塊中所有函數的全局符號表。因此,在模塊內使用全局變量時(shí),不用擔心與用戶(hù)定義的全局變量發(fā)生沖突。另一方面,可以用與訪(fǎng)問(wèn)模塊函數一樣的標記法,訪(fǎng)問(wèn)模塊的全局變量,modname.itemname
。
可以把其他模塊導入模塊。按慣例,所有 import
語(yǔ)句都放在模塊(或腳本)開(kāi)頭,但這不是必須的。導入的模塊名存在導入模塊的全局符號表里。
import
語(yǔ)句有一個(gè)變體,可以直接把模塊里的名稱(chēng)導入到另一個(gè)模塊的符號表。例如:
>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這段代碼不會(huì )把模塊名導入到局部符號表里(因此,本例沒(méi)有定義 fibo
)。
還有一種變體可以導入模塊內定義的所有名稱(chēng):
>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
這種方式會(huì )導入所有不以下劃線(xiàn)(_
)開(kāi)頭的名稱(chēng)。大多數情況下,不要用這個(gè)功能,這種方式向解釋器導入了一批未知的名稱(chēng),可能會(huì )覆蓋已經(jīng)定義的名稱(chēng)。
注意,一般情況下,不建議從模塊或包內導入 *
, 因為,這項操作經(jīng)常讓代碼變得難以理解。不過(guò),為了在交互式編譯器中少打幾個(gè)字,這么用也沒(méi)問(wèn)題。
模塊名后使用 as
時(shí),直接把 as
后的名稱(chēng)與導入模塊綁定。
>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
與 import fibo
一樣,這種方式也可以有效地導入模塊,唯一的區別是,導入的名稱(chēng)是 fib
。
from
中也可以使用這種方式,效果類(lèi)似:
>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
備注
為了保證運行效率,每次解釋器會(huì )話(huà)只導入一次模塊。如果更改了模塊內容,必須重啟解釋器;僅交互測試一個(gè)模塊時(shí),也可以使用 importlib.reload()
,例如 import importlib; importlib.reload(modulename)
。
6.1.1. 以腳本方式執行模塊?
可以用以下方式運行 Python 模塊:
python fibo.py <arguments>
這項操作將執行模塊里的代碼,和導入模塊一樣,但會(huì )把 __name__
賦值為 "__main__"
。 也就是把下列代碼添加到模塊末尾:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
既可以把這個(gè)文件當腳本使用,也可以用作導入的模塊, 因為,解析命令行的代碼只有在模塊以 “main” 文件執行時(shí)才會(huì )運行:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34
導入模塊時(shí),不運行這些代碼:
>>> import fibo
>>>
這種操作常用于為模塊提供便捷用戶(hù)接口,或用于測試(把模塊當作執行測試套件的腳本運行)。
6.1.2. 模塊搜索路徑?
When a module named spam
is imported, the interpreter first searches for
a built-in module with that name. These module names are listed in
sys.builtin_module_names
. If not found, it then searches for a file
named spam.py
in a list of directories given by the variable
sys.path
. sys.path
is initialized from these locations:
輸入腳本的目錄(或未指定文件時(shí)的當前目錄)。
PYTHONPATH
(目錄列表,與 shell 變量PATH
的語(yǔ)法一樣)。The installation-dependent default (by convention including a
site-packages
directory, handled by thesite
module).
More details are at The initialization of the sys.path module search path.
備注
在支持 symlink 的文件系統中,輸入腳本目錄是在追加 symlink 后計算出來(lái)的。換句話(huà)說(shuō),包含 symlink 的目錄并 沒(méi)有 添加至模塊搜索路徑。
初始化后,Python 程序可以更改 sys.path
。運行腳本的目錄在標準庫路徑之前,置于搜索路徑的開(kāi)頭。即,加載的是該目錄里的腳本,而不是標準庫的同名模塊。 除非刻意替換,否則會(huì )報錯。詳見(jiàn) 標準模塊。
6.1.3. “已編譯的” Python 文件?
為了快速加載模塊,Python 把模塊的編譯版緩存在 __pycache__
目錄中,文件名為 module.version.pyc
,version 對編譯文件格式進(jìn)行編碼,一般是 Python 的版本號。例如,CPython 的 3.3 發(fā)行版中,spam.py 的編譯版本緩存為 __pycache__/spam.cpython-33.pyc
。使用這種命名慣例,可以讓不同 Python 發(fā)行版及不同版本的已編譯模塊共存。
Python 對比編譯版本與源碼的修改日期,查看它是否已過(guò)期,是否要重新編譯,此過(guò)程完全自動(dòng)化。此外,編譯模塊與平臺無(wú)關(guān),因此,可在不同架構系統之間共享相同的支持庫。
Python 在兩種情況下不檢查緩存。其一,從命令行直接載入模塊,只重新編譯,不存儲編譯結果;其二,沒(méi)有源模塊,就不會(huì )檢查緩存。為了支持無(wú)源文件(僅編譯)發(fā)行版本, 編譯模塊必須在源目錄下,并且絕不能有源模塊。
給專(zhuān)業(yè)人士的一些小建議:
在 Python 命令中使用
-O
或-OO
開(kāi)關(guān),可以減小編譯模塊的大小。-O
去除斷言語(yǔ)句,-OO
去除斷言語(yǔ)句和 __doc__ 字符串。有些程序可能依賴(lài)于這些內容,因此,沒(méi)有十足的把握,不要使用這兩個(gè)選項?!皟?yōu)化過(guò)的”模塊帶有opt-
標簽,并且文件通常會(huì )一小些。將來(lái)的發(fā)行版或許會(huì )改進(jìn)優(yōu)化的效果。從
.pyc
文件讀取的程序不比從.py
讀取的執行速度快,.pyc
文件只是加載速度更快。compileall
模塊可以為一個(gè)目錄下的所有模塊創(chuàng )建 .pyc 文件。本過(guò)程的細節及決策流程圖,詳見(jiàn) PEP 3147。
6.2. 標準模塊?
Python 自帶一個(gè)標準模塊的庫,它在 Python 庫參考(此處以下稱(chēng)為"庫參考" )里另外描述。 一些模塊是內嵌到編譯器里面的, 它們給一些雖并非語(yǔ)言核心但卻內嵌的操作提供接口,要么是為了效率,要么是給操作系統基礎操作例如系統調入提供接口。 這些模塊集是一個(gè)配置選項, 并且還依賴(lài)于底層的操作系統。 例如,winreg
模塊只在 Windows 系統上提供。一個(gè)特別值得注意的模塊 sys
,它被內嵌到每一個(gè) Python 編譯器中。sys.ps1
和 sys.ps2
變量定義了一些字符,它們可以用作主提示符和輔助提示符:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
只有解釋器用于交互模式時(shí),才定義這兩個(gè)變量。
變量 sys.path
是字符串列表,用于確定解釋器的模塊搜索路徑。該變量以環(huán)境變量 PYTHONPATH
提取的默認路徑進(jìn)行初始化,如未設置 PYTHONPATH
,則使用內置的默認路徑??梢杂脴藴柿斜聿僮餍薷脑撟兞浚?/p>
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
6.3. dir()
函數?
內置函數 dir()
用于查找模塊定義的名稱(chēng)。返回結果是經(jīng)過(guò)排序的字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
'__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
'__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
'_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
'warnoptions']
沒(méi)有參數時(shí),dir()
列出當前定義的名稱(chēng):
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
注意,該函數列出所有類(lèi)型的名稱(chēng):變量、模塊、函數等。
dir()
不會(huì )列出內置函數和變量的名稱(chēng)。這些內容的定義在標準模塊 builtins
里:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
'__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
'zip']
6.4. 包?
包是一種用“點(diǎn)式模塊名”構造 Python 模塊命名空間的方法。例如,模塊名 A.B
表示包 A
中名為 B
的子模塊。正如模塊可以區分不同模塊之間的全局變量名稱(chēng)一樣,點(diǎn)式模塊名可以區分 NumPy 或 Pillow 等不同多模塊包之間的模塊名稱(chēng)。
假設要為統一處理聲音文件與聲音數據設計一個(gè)模塊集(“包”)。聲音文件的格式很多(通常以擴展名來(lái)識別,例如:.wav
, .aiff
, .au
),因此,為了不同文件格式之間的轉換,需要創(chuàng )建和維護一個(gè)不斷增長(cháng)的模塊集合。為了實(shí)現對聲音數據的不同處理(例如,混聲、添加回聲、均衡器功能、創(chuàng )造人工立體聲效果),還要編寫(xiě)無(wú)窮無(wú)盡的模塊流。下面這個(gè)分級文件樹(shù)展示了這個(gè)包的架構:
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
導入包時(shí),Python 搜索 sys.path
里的目錄,查找包的子目錄。
Python 只把含 __init__.py
文件的目錄當成包。這樣可以防止以 string
等通用名稱(chēng)命名的目錄,無(wú)意中屏蔽出現在后方模塊搜索路徑中的有效模塊。 最簡(jiǎn)情況下,__init__.py
只是一個(gè)空文件,但該文件也可以執行包的初始化代碼,或設置 __all__
變量,詳見(jiàn)下文。
還可以從包中導入單個(gè)模塊,例如:
import sound.effects.echo
這段代碼加載子模塊 sound.effects.echo
,但引用時(shí)必須使用子模塊的全名:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
另一種導入子模塊的方法是 :
from sound.effects import echo
這段代碼還可以加載子模塊 echo
,不加包前綴也可以使用。因此,可以按如下方式使用:
echo.echofilter(input, output, delay=0.7, atten=4)
Import 語(yǔ)句的另一種變體是直接導入所需的函數或變量:
from sound.effects.echo import echofilter
同樣,這樣也會(huì )加載子模塊 echo
,但可以直接使用函數 echofilter()
:
echofilter(input, output, delay=0.7, atten=4)
注意,使用 from package import item
時(shí),item 可以是包的子模塊(或子包),也可以是包中定義的函數、類(lèi)或變量等其他名稱(chēng)。import
語(yǔ)句首先測試包中是否定義了 item;如果未在包中定義,則假定 item 是模塊,并嘗試加載。如果找不到 item,則觸發(fā) ImportError
異常。
相反,使用 import item.subitem.subsubitem
句法時(shí),除最后一項外,每個(gè) item 都必須是包;最后一項可以是模塊或包,但不能是上一項中定義的類(lèi)、函數或變量。
6.4.1. 從包中導入 *?
使用 from sound.effects import *
時(shí)會(huì )發(fā)生什么?理想情況下,該語(yǔ)句在文件系統查找并導入包的所有子模塊。這項操作花費的時(shí)間較長(cháng),并且導入子模塊可能會(huì )產(chǎn)生不必要的副作用,這種副作用只有在顯式導入子模塊時(shí)才會(huì )發(fā)生。
唯一的解決方案是提供包的顯式索引。import
語(yǔ)句使用如下慣例:如果包的 __init__.py
代碼定義了列表 __all__
,運行 from package import *
時(shí),它就是用于導入的模塊名列表。發(fā)布包的新版本時(shí),包的作者應更新此列表。如果包的作者認為沒(méi)有必要在包中執行導入 * 操作,也可以不提供此列表。例如,sound/effects/__init__.py
文件包含以下代碼:
__all__ = ["echo", "surround", "reverse"]
This would mean that from sound.effects import *
would import the three
named submodules of the sound.effects
package.
如果沒(méi)有定義 __all__
,from sound.effects import *
語(yǔ)句 不會(huì ) 把包 sound.effects
中所有子模塊都導入到當前命名空間;該語(yǔ)句只確保導入包 sound.effects
(可能還會(huì )運行 __init__.py
中的初始化代碼),然后,再導入包中定義的名稱(chēng)。這些名稱(chēng)包括 __init__.py
中定義的任何名稱(chēng)(以及顯式加載的子模塊),還包括之前 import
語(yǔ)句顯式加載的包里的子模塊。請看以下代碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
本例中,執行 from...import
語(yǔ)句時(shí),將把 echo
和 surround
模塊導入至當前命名空間,因為,它們是在 sound.effects
包里定義的。(該導入操作在定義了 __all__
時(shí)也有效。)
雖然,可以把模塊設計為用 import *
時(shí)只導出遵循指定模式的名稱(chēng),但仍不提倡在生產(chǎn)代碼中使用這種做法。
記住,使用 from package import specific_submodule
沒(méi)有任何問(wèn)題! 實(shí)際上,除了導入模塊使用不同包的同名子模塊之外,這種方式是推薦用法。
6.4.2. 子包參考?
包中含有多個(gè)子包時(shí)(與示例中的 sound
包一樣),可以使用絕對導入引用兄弟包中的子模塊。例如,要在模塊 sound.filters.vocoder
中使用 sound.effects
包的 echo
模塊時(shí),可以用 from sound.effects import echo
導入。
還可以用 import 語(yǔ)句的 from module import name
形式執行相對導入。這些導入語(yǔ)句使用前導句點(diǎn)表示相對導入中的當前包和父包。例如,相對于 surround
模塊,可以使用:
from . import echo
from .. import formats
from ..filters import equalizer
注意,相對導入基于當前模塊名。因為主模塊名是 "__main__"
,所以 Python 程序的主模塊必須始終使用絕對導入。
6.4.3. 多目錄中的包?
包還支持特殊屬性 __path__
。該屬性初始化為在包的 __init__.py
文件中的代碼執行前所在的目錄名列表。這個(gè)變量可以修改,但這樣做會(huì )影響將來(lái)搜索包中模塊和子包的操作。
這個(gè)功能雖然不常用,但可用于擴展包中的模塊集。
備注
- 1
實(shí)際上,函數定義也是“可執行”的“語(yǔ)句”;執行模塊級函數定義時(shí),函數名將被導入到模塊的全局符號表。