### 6. 模塊
如果你退出 Python 解釋器并重新進入,你做的任何定義(變量和方法)都會丟失。因此,如果你想要編寫一些更大的程序,最好使用文本編輯器先編寫好,然后運行這個文件。這就是所謂的創建 *腳本*。隨著你的程序變得越來越長,你可能想要將它分成幾個文件,這樣更易于維護。你還可能想在幾個程序中使用你已經編寫好的函數,而不用把函數拷貝到每個程序中。
為了支持這個功能,Python 有種方法可以把你定義的內容放到一個文件中,然后在腳本或者交互方式中使用。這種文件稱為*模塊*;模塊中的定義可以 *導入* 到其它模塊或 *主模塊* 中。
模塊是包含 Python 定義和聲明的文件。文件名就是模塊名加上.py 后綴。在模塊里面,模塊的名字(是一個字符串)可以由全局變量 __name__ 的值得到。例如,用你喜歡的文本編輯器在當前目錄下創建一個名為 fibo.py 的文件,內容如下:
~~~
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
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
~~~
現在進入 Python 解釋器并使用下面的命令導入這個模塊:
~~~
>>> import fibo
~~~
這不會直接把 fibo 中定義的函數的名字導入當前的符號表中;它只會把模塊名字 fibo 導入其中。你可以通過模塊名訪問這些函數:
~~~
>>> 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'
~~~
如果你打算頻繁使用一個函數,可以將它賦給一個本地的變量:
~~~
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
~~~
### 6.1. 深入模塊
模塊可以包含可執行語句以及函數的定義。這些語句通常用于初始化模塊。它們只在 *第一次* 導入時執行。[[1]](#)(如果文件以腳本的方式執行,它們也會運行。)
每個模塊都有自己的私有符號表,模塊內定義的所有函數用其作為全局符號表。因此,模塊的作者可以在模塊里使用全局變量,而不用擔心與某個用戶的全局變量有沖突。另一方面,如果你知道自己在做什么,你可以使用引用模塊函數的表示法訪問模塊的全局變量,modname.itemname。
模塊中可以導入其它模塊。習慣上將所有的 [import](#) 語句放在模塊(或者腳本)的開始,但這不是強制性的。被導入的模塊的名字放在導入模塊的全局符號表中。
[import](#) 語句的一個變體直接從被導入的模塊中導入名字到導入模塊的符號表中。例如:
~~~
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
~~~
這不會把模塊名導入到本地的符號表中(所以在本例中,fibo 將沒有定義)。
還有種方式可以導入模塊中定義的所有名字:
~~~
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
~~~
這種方式不會導入以下劃線 (_) 開頭的名稱。
注意一般情況下不贊成從一個模塊或包中導入 * ,因為這通常會導致代碼很難讀。不過,在交互式會話中這樣用是可以的,它可以讓你少敲一些代碼。
注意
出于性能考慮,每個模塊在每個解釋器會話中只導入一遍。因此,如果你修改了你的模塊,你必需重新啟動解釋器 —— 或者,如果你就是想交互式的測試這么一個模塊,可以使用[reload()](# "reload"),例如reload(modulename)。
#### 6.1.1. 執行模塊
當你用下面的方式運行一個 Python 模塊
~~~
python fibo.py <arguments>
~~~
模塊中的代碼將會被執行,就像導入它一樣,不過此時 __name__ 被設置為 "__main__" 。也就是說,如果你在模塊后加入如下代碼:
~~~
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
~~~
就可以讓此文件既可以作為可執行的腳本,也可以當作可以導入的模塊,因為解析命令行的那部分代碼只有在模塊作為 “main” 文件執行時才被調用:
~~~
$ python fibo.py 50
1 1 2 3 5 8 13 21 34
~~~
如果模塊是被導入的,將不會運行這段代碼:
~~~
>>> import fibo
>>>
~~~
這種方法通常用來為模塊提供一個方便的用戶接口,或者用來測試(例如直接運行腳本會執行一組測試用例)。
#### 6.1.2. 模塊搜索路徑
當導入一個名為 spam 的模塊時,解釋器首先搜索具有該名稱的內置模塊。如果沒有找到,它會接著到 [sys.path](# "sys.path") 變量給出的目錄中查找名為 spam.py 的文件。[sys.path](# "sys.path") 變量的初始值來自這些位置:
- 腳本所在的目錄(或當前目錄)。
- [PYTHONPATH](#) (一個包含目錄名的列表,與 shell 變量PATH 的語法相同)。
- 與安裝相關的默認值。
初始化后,Python 程序可以修改[sys.path](# "sys.path")。腳本所在的目錄被放置在搜索路徑的最開始,也就是在標準庫的路徑之前。這意味著將會加載當前目錄中的腳本,庫目錄中具有相同名稱的模塊不會被加載。除非你是有意想替換標準庫,否則這應該被當成是一個錯誤。更多信息請參閱 [*標準模塊*](#) 小節。
#### 6.1.3. "編譯過的" Python 文件
對于使用了大量標準模塊的簡短程序,有一個提高啟動速度的重要方法,如果在spam.py所在的目錄下存在一個名為spam.pyc的文件,它會被視為spam模塊的已“編譯”版本。生成spam.pyc文件時,spam.py文件的修改時間會被記錄在spam.pyc文件中,如果時間不匹配,.pyc文件將被忽略。
通常情況下,您不需要做任何事情來創建spam.pyc文件。每當spam.py編譯成功,會嘗試向spam.pyc寫入編譯的版本。嘗試失敗也不會出現錯誤;如果出于任何原因文件寫入不完全,生成的 spam.pyc 文件將被當作是無效的,并且在以后會被忽略。spam.pyc文件的內容與平臺無關,因此Python模塊的目錄可以在不同體系結構的機器間共享。
部分高級技巧:
- 當以[*-O*](#)標志調用Python解釋器時,將生成優化的代碼并保存在.pyo文件中。目前的優化不會幫助太多;它只是刪除[assert](#)語句。當使用[*-O*](#)時,將優化*所有*的[*字節碼*](#);將忽略.pyc文件并將.py文件編譯成優化的字節碼。
- 向 Python 解釋器傳遞兩個[*-O*](#)標志 ([*-OO*](#)) 會導致字節碼編譯器執行優化,極少數情況下,這可能導致程序故障。目前只會從字節碼中刪除__doc__字符串,使.pyo文件更加緊湊。因為某些程序可能會依賴于具有這些可用,您應只使用此選項,在你知道你在做什么時。
- 程序不能運行得更快,不管它是從哪種文件(.py ; .pyc或.pyo)中讀取。唯一加速的是.pyc或.pyo文件的加載速度。
- 當在命令行使用腳本名稱來運行腳本時,腳本的字節碼是不會被寫入相應的.pyc或.pyo文件中的。因此,如果將大部分與啟動無關的代碼移到一個模塊中而以導入模塊的方式在啟動腳本中導入這個模塊就可節省一些啟動時間。也可以直接在命令行上運行.pyc或.pyo文件。
- 可以沒有相同的模塊文件spam.py時,使用文件spam.pyc (或spam.pyo時使用[*-O*](#) )。這可用于發布不太容易進行反向工程的Python 代碼庫。
- 模塊[compileall](# "compileall: Tools for byte-compiling all Python source files in a directory tree.")可以為一個目錄中的所有模塊創建.pyc文件 (或.pyo文件時使用[*-O*](#) )。
### 6.2. 標準模塊
Python 帶有一個標準模塊庫,并發布有單獨的文檔叫Python 庫參考手冊(以下簡稱"庫參考手冊")。有些模塊被直接構建在解析器里;這些操作雖然不是語言核心的部分,但是依然被內建進來,一方面是效率的原因,另一方面是為了提供訪問操作系統原語如系統調用的功能。這些模塊是可配置的,也取決于底層的平臺。例如,winreg模塊只在Windows系統上提供。有一個特別的模塊值得注意:[sys](# "sys: Access system-specific parameters and functions."),它內置在每一個Python解析器中。變量sys.ps1和sys.ps2定義了主提示符和輔助提示符使用的字符串:
~~~
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print 'Yuck!'
Yuck!
C>
~~~
只有在交互式模式中,這兩個變量才有定義。
變量 sys.path 是一個字符串列表,它決定了解釋器搜索模塊的路徑。它初始的默認路徑來自于環境變量 [PYTHONPATH](#),如果 [PYTHONPATH](#) 未設置則來自于內置的默認值。你可以使用標準的列表操作修改它:
~~~
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
~~~
### 6.3. [dir()](# "dir")函數
內置函數 [dir()](# "dir") 用來找出模塊中定義了哪些名字。它返回一個排好序的字符串列表:
~~~
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__package__',
'__stderr__', '__stdin__', '__stdout__', '_clear_type_cache',
'_current_frames', '_getframe', '_mercurial', 'api_version', 'argv',
'builtin_module_names', 'byteorder', 'call_tracing', 'callstats',
'copyright', 'displayhook', 'dont_write_bytecode', 'exc_clear', 'exc_info',
'exc_traceback', 'exc_type', 'exc_value', 'excepthook', 'exec_prefix',
'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'gettotalrefcount', 'gettrace', 'hexversion',
'long_info', 'maxint', 'maxsize', 'maxunicode', 'meta_path', 'modules',
'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
'py3kwarning', 'setcheckinterval', 'setdlopenflags', 'setprofile',
'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'subversion',
'version', 'version_info', 'warnoptions']
~~~
如果不帶參數, [dir()](# "dir") 列出當前已定義的名稱:
~~~
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', '__package__', 'a', 'fib', 'fibo', 'sys']
~~~
注意它列出了所有類型的名稱: 變量、 模塊、 函數等。
[dir()](# "dir")不會列出內置的函數和變量的名稱。如果你想列出這些內容,它們定義在標準模塊 [__builtin__](# "__builtin__: The module that provides the built-in namespace.") 中:
~~~
>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError',
'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning',
'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True',
'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning',
'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__',
'__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring',
'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr',
'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright',
'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval',
'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset',
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input',
'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license',
'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next',
'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit',
'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round',
'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
~~~
### 6.4. 包
包是一種管理 Python 模塊命名空間的方式,采用“點分模塊名稱”。例如,模塊名 A.B 表示包A 中一個名為 B 的子模塊。就像模塊的使用讓不同模塊的作者不用擔心相互間的全局變量名稱一樣,點分模塊的使用讓包含多個模塊的包(例如 Numpy 和 Python Imaging Library)的作者也不用擔心相互之間的模塊重名。
假設你想要設計一系列模塊(或一個“包”)來統一處理聲音文件和聲音數據。現存很多種不同的聲音文件格式 (通常由它們的擴展名來識別,例如: .wav, .aiff, .au),因此你可能需要創建和維護不斷增長的模塊集合來支持各種文件格式之間的轉換。你可能還想針對音頻數據做很多不同的操作(比如混音,添加回聲,增加均衡器功能,創建人造立體聲效果),所以你還需要編寫一組永遠寫不完的模塊來處理這些操作。你的包可能會是這個結構(用分層的文件系統表示):
~~~
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
...
~~~
導入這個包時,Python 搜索 sys.path 中的目錄以尋找這個包的子目錄。
為了讓 Python 將目錄當做包,目錄下必須包含 __init__.py 文件;這樣做是為了防止一個具有常見名字(例如 string)的目錄無意中隱藏目錄搜索路徑中正確的模塊。最簡單的情況下,__init__.py 可以只是一個空的文件,但它也可以為包執行初始化代碼或設置__all__變量(稍后會介紹)。
用戶可以從包中導入單獨的模塊,例如:
~~~
import sound.effects.echo
~~~
這樣就加載了子模塊sound.effects.echo。它必須使用其完整名稱來引用。
~~~
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)
~~~
還有另一種變化方式是直接導入所需的函數或變量:
~~~
from sound.effects.echo import echofilter
~~~
這再次加載了子模塊echo,但這種方式使函數echofilter() 可以直接訪問:
~~~
echofilter(input, output, delay=0.7, atten=4)
~~~
注意使用frompackageimportitem時,item 可以是包的子模塊(或子包),也可以是包中定義的一些其它的名稱,比如函數、 類或者變量。import語句首先測試 item 在包中是否有定義;如果沒有,它假定它是一個模塊,并嘗試加載它。如果未能找到,則引發[ImportError](# "exceptions.ImportError")異常。
相反,使用類似 importitem.subitem.subsubitem 這樣的語法時,除了最后一項其它每項必須是一個包;最后一項可以是一個模塊或一個包,但不能是在前一個項目中定義的類、函數或變量。
#### 6.4.1. 從包中導入 *
那么現在如果用戶寫成 fromsound.effectsimport* 會發生什么?理想情況下,他應該是希望到文件系統中尋找包里面有哪些子模塊,并把它們全部導入進來。這可能需要很長時間,而且導入子模塊可能會產生想不到的副作用,這些作用本應該只有當子模塊是顯式導入時才會發生。
唯一的解決辦法是包的作者為包提供顯式的索引。[import](#) 語句使用以下約定: 如果包中的 __init__.py 代碼定義了一個名為__all__的列表,那么在遇到 frompackageimport*語句的時候,應該把這個列表中的所有模塊名字導入。當包有新版本包發布時,就需要包的作者更新這個列表了。如果包的作者認為不可以用 import * 方式導入它們的包,也可以決定不支持它。例如,文件sound/effects/__init__.py可以包含下面的代碼:
~~~
__all__ = ["echo", "surround", "reverse"]
~~~
這意味著 fromsound.effectsimport* 將導入sound 包的三個子模塊。
如果 __all__ 沒有定義,fromsound.effectsimport* 語句 *不* 會從 sound.effects 包中導入所有的子模塊到當前命名空間;它只保證 sound.effects 包已經被導入(可能會運行 __init__.py 中的任何初始化代碼),然后導入包中定義的任何名稱。這包括由 __init__.py 定義的任何名稱(以及它顯式加載的子模塊)。還包括這個包中已經由前面的[import](#) 語句顯式加載的子模塊。請考慮此代碼:
~~~
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
~~~
在這個例子中,執行 from...import 語句時,echo 和 surround 模塊被導入到當前命名空間是因為它們在sound.effects中有定義。(定義了 __all__時也會同樣工作。)
雖然某些模塊設計成使用 import* 時只導出符合特定模式的名稱,在產品代碼中使用這種寫法仍然是不好的做法。
記住,使用 fromPackageimportspecific_submodule 一點沒錯 !事實上,這是推薦的寫法,除非導入的模塊需要使用其它包中的同名子模塊。
#### 6.4.2. 包內引用
子模塊通常需要相互引用。例如,surround 模塊可能會使用 echo 模塊。事實上,這種引用是如此常見以致 [import](#) 語句會在標準模塊搜索路徑之前首先在所在的包中查找。因此,surround 模塊可以簡單地使用importecho 或 fromechoimportechofilter。如果在當前包(當前模塊屬于其子模塊的包)中未找到要導入的模塊,[import](#) 語句會查找具有給定名稱的頂級模塊。
如果一個包是子包(比如例子中的 sound 包),你可以使用絕對導入來引用兄弟包的子模塊。例如,如果模塊 sound.filters.vocoder 需要使用sound.effects 包中的 echo 模塊,它可以使用 fromsound.effectsimportecho。
Python 2.5 開始,除了上面描述的隱式相對導入,你可以使用frommoduleimportname 的導入形式編寫顯式相對導入。這些顯式的相對導入使用前導的點號表示相對導入的是從當前包還是上級的包。以 surround 模塊為例,你可以使用:
~~~
from . import echo
from .. import formats
from ..filters import equalizer
~~~
注意,顯式和隱式相對導入都基于當前模塊的名稱。因為主模塊的名字總是 "__main__" ,Python 應用程序的主模塊應該總是用絕對導入。
#### 6.4.3. 包含多個目錄的包
包還支持一個特殊的屬性, __path__。在文件運行之前,該變量被初始化為一個包含 __init__.py 所在目錄的列表。這個變量可以修改;這樣做會影響未來包中包含的模塊和子包的搜索。
雖然通常不需要此功能,它可以用于擴展包中的模塊的集合。
腳注
| [[1]](#) | 實際上,函數的定義也是‘執行過’的‘語句’;模塊級別的函數定義的執行將函數名放入該模塊的全局符號表中。 |
|-----|-----|
- Python 2 教程
- 1. 吊吊你的胃口
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限
- Python 2 標準庫
- 1. 引言
- 2. 內建函數
- 3. 不太重要的內建函數
- 4. 內建的常量
- 5. 內建的類型
- 6. 內建的異常
- 7. String Services
- 8. Data Types
- 9. Numeric and Mathematical Modules
- 10. File and Directory Access
- 11. Data Persistence
- 13. File Formats
- 14. Cryptographic Services
- 15. Generic Operating System Services
- 16. Optional Operating System Services
- 17. Interprocess Communication and Networking
- 18. Internet Data Handling
- 20. Internet Protocols and Support
- 26. Debugging and Profiling
- 28. Python Runtime Services
- Python 2 語言參考
- 1. 簡介
- 2. 詞法分析
- 3. 數據模型
- 4. 執行模型
- 5. 表達式
- 6. 簡單語句
- 7. 復合語句
- 8. 頂層的組件
- 9. 完整的語法規范
- Python 3 教程
- 1. 引言
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限