如果你退出 Python 解釋器并重新進入,你做的任何定義(變量和方法)都會丟失。因此,如果你想要編寫一些更大的程序,為準備解釋器輸入使用一個文本編輯器會更好,并以那個文件替代作為輸入執(zhí)行。這就是傳說中的腳本。隨著你的程序變得越來越長,你可能想要將它分割成幾個更易于維護的文件。你也可能想在不同的程序中使用順手的函數(shù),而不是把代碼在它們之間中拷來拷去。
為了滿足這些需要,Python 提供了一個方法可以從文件中獲取定義,在腳本或者解釋器的一個交互式實例中使用。這樣的文件被稱為模塊;模塊中的定義可以導(dǎo)入到另一個模塊或主模塊中(在腳本執(zhí)行時可以調(diào)用的變量集位于最高級,并且處于計算器模式)。
模塊是包括 Python 定義和聲明的文件。文件名就是模塊名加上.py后綴。模塊的模塊名(做為一個字符串)可以由全局變量__name__得到。例如,你可以用自己慣用的文件編輯器在當(dāng)前目錄下創(chuàng)建一個叫fibo.py 的文件,錄入如下內(nèi)容:
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print(b, end=' ')
a, b = b, a+b
print()
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
現(xiàn)在進入 Python 解釋器并使用以下命令導(dǎo)入這個模塊:
>>> import fibo
這樣做不會直接把fibo中的函數(shù)導(dǎo)入當(dāng)前的語義表;它只是引入了模塊名fibo。你可以通過模塊名按如下方式訪問這個函數(shù):
>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
如果打算頻繁使用一個函數(shù),你可以將它賦予一個本地變量:
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
除了包含函數(shù)定義外,模塊也可以包含可執(zhí)行語句。這些語句一般用來初始化模塊。他們僅在第一次被導(dǎo)入的地方執(zhí)行一次。[1]
每個模塊都有自己私有的符號表,被模塊內(nèi)所有的函數(shù)定義作為全局符號表使用。因此,模塊的作者可以在模塊內(nèi)部使用全局變量,而無需擔(dān)心它與某個用戶的全局變量意外沖突。從另一個方面講,如果你確切的知道自己在做什么,你可以使用引用模塊函數(shù)的表示法訪問模塊的全局變量,modname.itemname?。
模塊可以導(dǎo)入其他的模塊。一個(好的)習(xí)慣是將所有的import語句放在模塊的開始(或者是腳本),這并非強制。被導(dǎo)入的模塊名會放入當(dāng)前模塊的全局符號表中。
import語句的一個變體直接從被導(dǎo)入的模塊中導(dǎo)入命名到本模塊的語義表中。例如:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這樣不會從局域語義表中導(dǎo)入模塊名(如上所示,fibo沒有定義)。
甚至有種方式可以導(dǎo)入模塊中的所有定義:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這樣可以導(dǎo)入所有除了以下劃線(_)開頭的命名。在大多數(shù)情況下,Python 程序員不使用此工具,因為它引入了一個未知的名稱翻譯,可能隱藏一些你已經(jīng)定義的事情。
需要注意的是在實踐中往往不鼓勵從一個模塊或包中使用*導(dǎo)入所有,因為這樣會讓代碼變得很難讀。不過,在交互式會話中這樣用很方便省力。
注意
出于性能考慮,每個模塊在每個解釋器會話中只導(dǎo)入一遍。因此,如果你修改了你的模塊,需要重啟解釋器——或者,如果你就是想交互式的測試這么一個模塊,可以用reload()重新加載,例如reload(modulename)。
當(dāng)你使用以下方式運行 Python 模塊時,模塊中的代碼便會被執(zhí)行:
python fibo.py <arguments>
模塊中的代碼會被執(zhí)行,就像導(dǎo)入它一樣,不過此時 __name__被設(shè)置為 "__main__"。這相當(dāng)于,如果你在模塊后加入如下代碼:
If __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
就可以讓此文件像作為模塊導(dǎo)入時一樣作為腳本執(zhí)行。此代碼只有在模塊作為 “main” 文件執(zhí)行時才被調(diào)用:
$ python fibo.py 50
1 1 2 3 5 8 13 21 34
如果模塊被導(dǎo)入,不會執(zhí)行這段代碼:
>>> import fibo
>>>
這通常用來為模塊提供一個便于測試的用戶接口(將模塊作為腳本執(zhí)行測試需求)。
導(dǎo)入一個叫spam的模塊時,解釋器先在當(dāng)前目錄中搜索名為spam.py的文件。如果沒有找到的話,接著會到sys.path變量中給出的目錄列表中查找。sys.path變量的初始值來自如下:
包含輸入腳本的目錄(沒有指定文件的當(dāng)前目錄)
PYTHONPATH 環(huán)境(目錄名的列表,與 shell 變量路徑的語法相同)
installation-dependent 默認。
注意:支持符號鏈接的文件系統(tǒng),在符號鏈接后,包含輸入腳本的目錄被計算,換句話說包含符號鏈接的目錄不是添加到模塊搜索路徑
初始化后,Python 程序可以修改sys.path。包含正在運行的腳本的目錄被放置在搜索路徑的開始,也就是在標(biāo)準庫路徑。在庫目錄中,這意味著該目錄中的腳本將被加載,而不是模塊名稱相同的被加載。這是一個錯誤,除非更換是有目的的。有關(guān)更多信息,請參見部分標(biāo)準模塊。
為了加速加載模塊,Python 緩存編譯版本中每個模塊的__pycache__目錄在名稱module.version.pyc下,那里的版本編譯的文件編碼格式;它一般包含 Python 的版本號。例如,在當(dāng)前的版本 3.3 的編譯版本將緩存為spam.py?__pycache__?/?spam.cpython-33.pyc。這個模塊允許 compiled 命名不同的版本,和不同版本的 Python 共存。
Python 檢查對編譯版本源碼的修改日期是否過期,過期需要重新編譯。這是一個完全自動化的過程。同時,已編譯的模塊是平臺獨立的,所以同一個庫可以與不同的結(jié)構(gòu)系統(tǒng)之間的共享。
Python 不檢查緩存兩種情況。首先,它總是重新編譯,不存儲結(jié)果的模塊的加載,直接從命令行。第二,如果沒有源模塊,它不檢查緩存。為了支持一個非源(只編譯)分布,編譯的模塊必須在源目錄,并且肯定不是一個源模塊。
部分高級技巧:
-o或面向?qū)ο蟮拈_關(guān)上的 Python 命令來減少編譯的模塊的大小。-O 開關(guān)去除 assert 語句,該對象開關(guān)消除 Assert 語句和 __doc__字符串。因為某些程序依賴于這些變量的可用性,你應(yīng)該只在確定無誤的場合使用這一選項。“優(yōu)化”,又一個 .pyo 模塊而不是一個.pyc,通常較小。未來的版本可能會改變優(yōu)化的影響。 .pyc 文件或.pyo 文件中的程序不會比來自.py 文件的運行更快; .pyc或.pyo文件只是在 它們加載的時候更快一些。 compileall模塊可以為指定目錄中的所有模塊創(chuàng)建.pyc文件(或者使用-O參數(shù)創(chuàng)建.pyo文件)。 Python 帶有一個標(biāo)準模塊庫,并發(fā)布有獨立的文檔,名為 Python 庫參考手冊(此后稱其為“庫參考手冊”)。有一些模塊內(nèi)置于解釋器之中,這些操作的訪問接口不是語言內(nèi)核的一部分,但是已經(jīng)內(nèi)置于解釋器了。這既是為了提高效率,也是為了給系統(tǒng)調(diào)用等操作系統(tǒng)原生訪問提供接口。這類模塊集合是一個依賴于底層平臺的配置選項。例如,winreg模塊只提供在 Windows 系統(tǒng)上才有。有一個具體的模塊值得注意:sys,這個模塊內(nèi)置于所有的 Python 解釋器。變量sys.ps1和sys.ps?定義了主提示符和輔助提示符字符串:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>
這兩個變量只在解釋器的交互模式下有意義。
變量sys.path是解釋器模塊搜索路徑的字符串列表。它由環(huán)境變量 PYTHONPATH初始化,如果沒有設(shè)定PYTHONPATH,就由內(nèi)置的默認值初始化。你可以用標(biāo)準的字符串操作修改它:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
內(nèi)置函數(shù) dir()用于按模塊名搜索模塊定義,它返回一個字符串類型的存儲列表:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__',
'__package__', '__stderr__', '__stdin__', '__stdout__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
'_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv',
'base_exec_prefix', 'base_prefix', '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',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
'thread_info', 'version', 'version_info', 'warnoptions']
無參數(shù)調(diào)用時,dir()函數(shù)返回當(dāng)前定義的命名:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
注意該列表列出了所有類型的名稱:變量,模塊,函數(shù),等等。
dir()?不會列出內(nèi)置函數(shù)和變量名。如果你想列出這些內(nèi)容,它們在標(biāo)準模塊內(nèi)置命令:中
>>> 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']
包通常是使用用“圓點模塊名”的結(jié)構(gòu)化模塊命名空間。例如,名為 A.B 的模塊表示了名為 A 的包中名為 B 的子模塊。正如同用模塊來保存不同的模塊架構(gòu)可以避免全局變量之間的相互沖突,使用圓點模塊名保存像 NumPy 或 Python Imaging Library 之類的不同類庫架構(gòu)可以避免模塊之間的命名沖突。
假設(shè)你現(xiàn)在想要設(shè)計一個模塊集(一個“包”)來統(tǒng)一處理聲音文件和聲音數(shù)據(jù)。存在幾種不同的聲音格式(通常由它們的擴展名來標(biāo)識,例如:.wav,.aiff,.au),于是,為了在不同類型的文件格式之間轉(zhuǎn)換,你需要維護一個不斷增長的包集合??赡苣氵€想要對聲音數(shù)據(jù)做很多不同的操作(例如混音,添加回聲,應(yīng)用平衡 功能,創(chuàng)建一個人造效果),所以你要加入一個無限流模塊來執(zhí)行這些操作。你的包可能會是這個樣子(通過分級的文件體系來進行分組):
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
...
當(dāng)導(dǎo)入這個包時,Python 通過sys.path搜索路徑查找包含這個包的子目錄。
為了讓 Python 將目錄當(dāng)做內(nèi)容包,目錄中必須包含__init__.py文件。 這是為了避免一個含有爛俗名字的目錄無意中隱藏了稍后在模塊搜索路徑中出現(xiàn)的有效模塊,比如 string 。 最簡單的情況下,只需要一個空的__init__.py文件即可。 當(dāng)然它也可以執(zhí)行包的初始化代碼,或者定義稍后介紹的__all__變量。
用戶可以每次只導(dǎo)入包里的特定模塊,例如:
import sound.effects.echo
這樣就導(dǎo)入了sound.effects.echo子模塊。它必需通過完整的名稱來引用。
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
導(dǎo)入包時有一個可以選擇的方式:
from sound.effects import echo
這樣就加載了echo子模塊,并且使得它在沒有包前綴的情況下也可以使用,所以它可以如下方式調(diào)用
echo.echofilter(input, output, delay=0.7, atten=4)
還有另一種變體用于直接導(dǎo)入函數(shù)或變量:
from sound.effects.echo import echofilter
這樣就又一次加載了echo子模塊,但這樣就可以直接調(diào)用它的echofilter() 函數(shù):
echofilter(input, output, delay=0.7, atten=4)
需要注意的是使用from package import item方式導(dǎo)入包時,這個子項(item)既可以是包中的一個子模塊(或一個子包),也可以是包中定義的其它命名,像函數(shù)、類或變量。import 語句首先核對是否包中有這個子項,如果沒有,它假定這是一個模塊,并嘗試加載它。如果沒有找到它,會引發(fā)一個ImportError異常。
相反,使用類似 import item.subitem.subsubitem這樣的語法時,這些子項必須是包,最后的子項可以是包或模塊,但不能是前面子項中定義的類、函數(shù)或變量。
那么當(dāng)用戶寫下 from sound.effects import *時會發(fā)生什么事?理想中,總是希望在文件系統(tǒng)中找出包中所有的子模塊,然后導(dǎo)入它們。這可能會花掉委有長時間,并且出現(xiàn)期待之外的邊界效應(yīng),導(dǎo)出了希望只能顯式導(dǎo)入的包。
對于包的作者來說唯一的解決方案就是給提供一個明確的包索引。import 語句按如下條件進行轉(zhuǎn)換:執(zhí)行 from package import *時,如果包中的 __init__.py代碼定義了一個名為 __all__的列表,就會按照列表中給出的模塊名進行導(dǎo)入。新版本的包發(fā)布時作者可以任意更新這個列表。如果包作者不想import * 的時候?qū)胨麄兊陌兴心K,那么也可能會決定不支持它( import *)。例如,sounds/effects/__init__.py這個文件可能包括如下代碼:
__all__ = ["echo", "surround", "reverse"]
這意味著 from sound.effects import *語句會從sound包中導(dǎo)入以上三個已命名的子模塊。
如果沒有定義__all__, from sound.effects import * 語句不會從sound.effects包中導(dǎo)入所有的子模塊。無論包中定義多少命名,只能確定的是導(dǎo)入了sound.effects包(可能會運行__init__.py中的初始化代碼)以及包中定義的所有命名會隨之導(dǎo)入。這樣就從__init__.py中導(dǎo)入了每一個命名(以及明確導(dǎo)入的子模塊)。同樣也包括了前述的import語句從包中明確導(dǎo)入的子模塊,考慮以下代碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在這個例子中,echo和surround模塊導(dǎo)入了當(dāng)前的命名空間,這是因為執(zhí)行 from...import語句時它們已經(jīng)定義在 sound.effects 包中了(定義了__all__ 時也會同樣工作)。
盡管某些模塊設(shè)計為使用 import *時它只導(dǎo)出符全某種模式的命名,仍然不建議在生產(chǎn)代碼中使用這種寫法。
記住,from Package import specific_submodule 沒有錯誤!事實上,除非導(dǎo)入的模塊需要使用其它包中的同名子模塊,否則這是推薦的寫法。
如果包中使用了子包結(jié)構(gòu)(就像示例中的sound包),可以按絕對位置從相鄰的包中引入子模塊。例如,如果sound.filters.vocoder包需要使用 sound.effects 包中的 echo 模塊,它可以from sound.effects import echo。
你可以用這樣的形式from module import name來寫顯式的相對位置導(dǎo)入。那些顯式相對導(dǎo)入用點號標(biāo)明關(guān)聯(lián)導(dǎo)入當(dāng)前和上級包。以surround模塊為例,你可以這樣用:
from . import echo
from .. import formats
from ..filters import equalizer
需要注意的是顯式或隱式相對位置導(dǎo)入都基于當(dāng)前模塊的命名。因為主模塊的名字總是"__main__",Python 應(yīng)用程序的主模塊應(yīng)該總是用絕對導(dǎo)入。
包支持一個更為特殊的特性,__path__。 在包的__init__.py文件代碼執(zhí)行之前,該變量初始化一個目錄名列表。該變量可以修改,它作用于包中的子包和模塊的搜索功能。
這個功能可以用于擴展包中的模塊集,不過它不常用。
腳注
[1] 事實上函數(shù)定義既是“聲明”又是“可執(zhí)行體”;執(zhí)行體由函數(shù)在模塊全局語義表中的命名導(dǎo)入。