### 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, 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
~~~
現在進入 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
~~~
這種方式不會導入以下劃線 (_) 開頭的名稱。大多數情況下Python程序員不會使用這個便利的方法,因為它會引入一系列未知的名稱到解釋器中,這很可能隱藏你已經定義的一些東西。
注意一般情況下不贊成從一個模塊或包中導入 * ,因為這通常會導致代碼很難讀。不過,在交互式會話中這樣用是可以的,它可以讓你少敲一些代碼。
注意
出于性能考慮,每個模塊在每個解釋器會話中只導入一遍。因此,如果你修改了你的模塊,你必需重新啟動解釋器 —— 或者,如果你就是想交互式的測試這么一個模塊,可以使用[imp.reload()](# "imp.reload"), 例如 importimp;imp.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 的語法相同)。
- 與安裝相關的默認值。
Note
在支持符號連接的文件系統中,輸入的腳本所在的目錄是符號連接指向的目錄。換句話說也就是包含符號鏈接的目錄**不**會被加到目錄搜索路徑中。
初始化后,Python 程序可以修改[sys.path](# "sys.path")。腳本所在的目錄被放置在搜索路徑的最開始,也就是在標準庫的路徑之前。這意味著將會加載當前目錄中的腳本,庫目錄中具有相同名稱的模塊不會被加載。除非你是有意想替換標準庫,否則這應該被當成是一個錯誤。更多信息請參閱 [*標準模塊*](#) 小節。
#### 6.1.3. "編譯過的" Python 文件
為了加快加載模塊的速度,Python會在__pycache__目錄下以module.*version*.pyc名字緩存每個模塊編譯后的版本,這里的版本編制了編譯后文件的格式。它通常會包含Python 的版本號。例如,在CPython 3.3版中,spam.py 編譯后的版本將緩存為__pycache__/spam.cpython-33.pyc。這種命名約定允許由不同發布和不同版本的Python編譯的模塊同時存在。
Python會檢查源文件與編譯版的修改日期以確定它是否過期并需要重新編譯。這是完全自動化的過程。同時,編譯后的模塊是跨平臺的,所以同一個庫可以在不同架構的系統之間共享。
Python 不檢查在兩個不同環境中的緩存。首先,它會永遠重新編譯而且不會存儲直接從命令行加載的模塊。其次,如果沒有源模塊它不會檢查緩存。若要支持沒有源文件(只有編譯版)的發布,編譯后的模塊必須在源目錄下,并且必須沒有源文件的模塊。
部分高級技巧:
-
{{s.58}}{{s.59}}{{s.60}}{{s.61}}{{s.62}}
-
{{s.63}}{{s.64}}{{s.65}}
-
{{s.66}}{{s.67}}
-
{{s.68}}{{s.69}}{{s.70}}
-
{{s.71}}{{條例}}
-
{{}} s.73
### 6.2. 標準模塊
Python 帶有一個標準模塊庫,并發布有單獨的文檔叫Python 庫參考手冊(以下簡稱"庫參考手冊")。有些模塊被直接構建在解析器里;這些操作雖然不是語言核心的部分,但是依然被內建進來,一方面是效率的原因,另一方面是為了提供訪問操作系統原語如系統調用的功能。這些模塊是可配置的,也取決于底層的平臺。例如,[winreg](# "winreg: Routines and objects for manipulating the Windows registry. (Windows)") 模塊只在 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__', '__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']
~~~
如果不帶參數, [dir()](# "dir") 列出當前已定義的名稱:
~~~
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
~~~
注意它列出了所有類型的名稱: 變量、 模塊、 函數等。
[dir()](# "dir")不會列出內置的函數和變量的名稱。如果你想列出這些內容,它們定義在標準模塊 [builtins](# "builtins: The module that provides the built-in namespace.") 中:
~~~
>>> 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. 包
包是一種管理 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](# "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. 包內引用
如果一個包是子包(比如例子中的 sound 包),你可以使用絕對導入來引用兄弟包的子模塊。例如,如果模塊 sound.filters.vocoder 需要使用sound.effects 包中的 echo 模塊,它可以使用 fromsound.effectsimportecho。
你還可以用frommoduleimportname形式的import語句進行相對導入。這些導入使用前導的點號表示相對導入的是從當前包還是上級的包。以 surround 模塊為例,你可以使用:
~~~
from . import echo
from .. import formats
from ..filters import equalizer
~~~
注意,相對導入基于當前模塊的名稱。因為主模塊的名字總是 "__main__" ,Python 應用程序的主模塊必須總是用絕對導入。
#### 6.4.3. 包含多個目錄的包
包還支持一個特殊的屬性,[__path__](# "__path__")。該變量初始化一個包含 __init__.py 所在目錄的列表。這個變量可以修改;這樣做會影響未來包中包含的模塊和子包的搜索。
雖然通常不需要此功能,它可以用于擴展包中的模塊的集合。
腳注
| [[1]](#) | In fact function definitions are also ‘statements’ that are ‘executed’; the execution of a module-level function definition enters the function name in the module’s global symbol table. |
|-----|-----|
- 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. 浮點數運算:問題和局限