首頁 > Python教程 > Python基礎教程 > Python入門教程之模塊

Python入門教程之模塊

時間:2019-08-14    來源:互聯網

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  

這樣可以導入所有除了以下劃線( _ )開頭的命名。

需要注意的是在實踐中往往不鼓勵從一個模塊或包中使用 * 導入所有,因為這樣會讓代碼變得很難讀。不過,在交互式會話中這樣用很方便省力。

Note

出于性能考慮,每個模塊在每個解釋器會話中只導入一遍。因此,如果你修改了你的模塊,需要重啟解釋器;或者,如果你就是想交互式的測試這么一個模塊,可以用 imp.reload() 重新加載,例如 import imp; 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 的模塊時,解釋器先在當前目錄中搜索名為 spam.py 的文件。如果沒有找到的話,接著會到 sys.path 變量中給出的目錄列表中查找。 sys.path 變量的初始值來自如下:

  • 輸入腳本的目錄(當前目錄)。

  • 環境變量 PYTHONPATH 表示的目錄列表中搜索

    (這和 shell 變量  PATH 具有一樣的語法,即一系列目錄名的列表)。

  • Python 默認安裝路徑中搜索。

    Note

    在支持符號連接的文件系統中,輸入的腳本所在的目錄是符號連接指向的目錄。 換句話說也就是包含符號鏈接的目錄不會被加到目錄搜索路徑中。

實際上,解釋器由 sys.path 變量指定的路徑目錄搜索模塊,該變量初始化時默認包含了輸入腳本(或者當前目錄), PYTHONPATH 和安裝目錄。這樣就允許 Python 程序了解如何修改或替換模塊搜索目錄。需要注意的是由于這些目錄中包含有搜索路徑中運行的腳本,所以這些腳本不應該和標準模塊重名,否則在導入模塊時 Python 會嘗試把這些腳本當作模塊來加載。這通常會引發錯誤。請參見 標準模塊 以了解更多的信息。

6.1.3. “編譯的” Python 文件

為了加快加載模塊的速度,Python 會在 __pycache__ 目錄下以 module.version.pyc 名字緩存每個模塊編譯后的版本,這里的版本編制了編譯后文件的格式。它通常會包含 Python 的版本號。例如,在 CPython 3.3 版中,spam.py 編譯后的版本將緩存為 __pycache__/spam.cpython-33.pyc。這種命名約定允許由不同發布和不同版本的 Python 編譯的模塊同時存在。

Python 會檢查源文件與編譯版的修改日期以確定它是否過期并需要重新編譯。這是完全自動化的過程。同時,編譯后的模塊是跨平臺的,所以同一個庫可以在不同架構的系統之間共享。

Python 不檢查在兩個不同環境中的緩存。首先,它會永遠重新編譯而且不會存儲直接從命令行加載的模塊。其次,如果沒有源模塊它不會檢查緩存。若要支持沒有源文件(只有編譯版)的發布,編譯后的模塊必須在源目錄下,并且必須沒有源文件的模塊。

部分高級技巧:

  • 為了減少一個編譯模塊的大小,你可以在 Python 命令行中使用 -O 或者 -OO-O 參數刪除了斷言語句,-OO 參數刪除了斷言語句和 __doc__ 字符串。

    因為某些程序依賴于這些變量的可用性,你應該只在確定無誤的場合使用這一選項。“優化的” 模塊有一個 .pyo 后綴而不是 .pyc 后綴。未來的版本可能會改變優化的效果。

  • 來自 .pyc 文件或 .pyo 文件中的程序不會比來自 .py 文件的運行更快;.pyc 或 .pyo 文件只是在它們加載的時候更快一些。

  • compileall 模塊可以為指定目錄中的所有模塊創建 .pyc 文件(或者使用 -O 參數創建 .pyo 文件)。

  • 在 PEP 3147 中有很多關這一部分內容的細節,并且包含了一個決策流程。

6.2. 標準模塊

Python 帶有一個標準模塊庫,并發布有獨立的文檔,名為 Python 庫參考手冊(此后稱其為“庫參考手冊”)。有一些模塊內置于解釋器之中,這些操作的訪問接口不是語言內核的一部分,但是已經內置于解釋器了。這既是為了提高效率,也是為了給系統調用等操作系統原生訪問提供接口。這類模塊集合是一個依賴于底層平臺的配置選項。例如,winreg 模塊只提供在 Windows 系統上才有。有一個具體的模塊值得注意: sys ,這個模塊內置于所有的 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() 用于按模塊名搜索模塊定義,它返回一個字符串類型的存儲列表:

 >>> 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() 函數返回當前定義的命名:

 >>> a = [1, 2, 3, 4, 5]  >>> import fibo  >>> fib = fibo.fib  >>> dir()  ['__builtins__', '__doc__', '__file__', '__name__', 'a', 'fib', 'fibo', 'sys']  

注意該列表列出了所有類型的名稱:變量,模塊,函數,等等。

dir() 不會列出內置函數和變量名。如果你想列出這些內容,它們在標準模塊 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. 包

包通常是使用用“圓點模塊名”的結構化模塊命名空間。例如,名為 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)  

需要注意的是使用 from package import item 方式導入包時,這個子項(item)既可以是包中的一個子模塊(或一個子包),也可以是包中定義的其它命名,像函數、類或變量。import 語句首先核對是否包中有這個子項,如果沒有,它假定這是一個模塊,并嘗試加載它。如果沒有找到它,會引發一個 ImportError 異常。

相反,使用類似 import item.subitem.subsubitem 這樣的語法時,這些子項必須是包,最后的子項可以是包或模塊,但不能是前面子項中定義的類、函數或變量。

6.4.1. 從 * 導入包

那么當用戶寫下 from sound.effects import * 時會發生什么事?理想中,總是希望在文件系統中找出包中所有的子模塊,然后導入它們。這可能會花掉很長時間,并且出現期待之外的邊界效應,導出了希望只能顯式導入的包。

對于包的作者來說唯一的解決方案就是給提供一個明確的包索引。import 語句按如下條件進行轉換:執行 from package import * 時,如果包中的 __init__.py 代碼定義了一個名為 __all__ 的列表,就會按照列表中給出的模塊名進行導入。新版本的包發布時作者可以任意更新這個列表。如果包作者不想 import * 的時候導入他們的包中所有模塊,那么也可能會決定不支持它( import * )。例如, sound/effects/__init__.py 這個文件可能包括如下代碼:

 __all__ = ["echo", "surround", "reverse"]  

這意味著 from sound.effects import * 語句會從 sound 包中導入以上三個已命名的子模塊。

如果沒有定義 __all__ , from sound.effects import * 語句 不會 從 sound.effects 包中導入所有的子模塊。無論包中定義多少命名,只能確定的是導入了 sound.effects 包(可能會運行 __init__.py 中的初始化代碼)以及包中定義的所有命名會隨之導入。這樣就從 __init__.py 中導入了每一個命名(以及明確導入的子模塊)。同樣也包括了前述的 import 語句從包中明確導入的子模塊,考慮以下代碼:

 import sound.effects.echo  import sound.effects.surround  from sound.effects import *  

在這個例子中,echo 和 surround 模塊導入了當前的命名空間,這是因為執行 from...import 語句時它們已經定義在 sound.effects 包中了(定義了 __all__ 時也會同樣工作)。

盡管某些模塊設計為使用 import * 時它只導出符合某種規范/模式的命名,仍然不建議在生產代碼中使用這種寫法。

記住,from Package import specific_submodule 沒有錯誤!事實上,除非導入的模塊需要使用其它包中的同名子模塊,否則這是推薦的寫法。

6.4.2. 包內引用

如果包中使用了子包結構(就像示例中的 sound 包),可以按絕對位置從相鄰的包中引入子模塊。例如,如果 sound.filters.vocoder 包需要使用 sound.effects 包中的 echo 模塊,它可以 from sound.Effects import echo。

你可以用這樣的形式 from module import name 來寫顯式的相對位置導入。那些顯式相對導入用點號標明關聯導入當前和上級包。以 surround 模塊為例,你可以這樣用:

 from . import echo  from .. import formats  from ..filters import equalizer  

需要注意的是顯式或隱式相對位置導入都基于當前模塊的命名。因為主模塊的名字總是 "__main__",Python 應用程序的主模塊應該總是用絕對導入。

6.4.3. 多重目錄中的包

包支持一個更為特殊的特性, __path__。 在包的 __init__.py 文件代碼執行之前,該變量初始化一個目錄名列表。該變量可以修改,它作用于包中的子包和模塊的搜索功能。

這個功能可以用于擴展包中的模塊集,不過它不常用。

Footnotes

[1] 事實上函數定義既是“聲明”又是“可執行體”;執行體由函數在模塊全局語義表中的命名導入。

 

相關推薦
Python入門簡介 Python能做什么?
調用Python 解釋器 什么是Python 解釋器?
Python3 簡介
Python入門教程之流程控制
Python入門教程之數據結構
Python入門教程之輸入和輸出
Python入門教程之錯誤和異常處理
Python入門教程之類
Python入門教程之標準庫概覽
Python入門教程之標準庫瀏覽 – Part II
Python入門教程之虛擬環境和包
Python入門教程之浮點數算法:爭議和限制

精彩推薦

熱門教程

忍者法宝试玩
快乐十分杀号技巧 四川成都麻将下载 内蒙古快三规则及中奖规则 后二平刷稳赚方案 网上做什么赚钱日赚50 天猫优惠券怎么做赚钱 东方6+1 广西快三玩法技巧 第五名时时彩计划稳定版 在手机上炸金花有什么技巧 下一个东北玩法打麻将 足球比分90vs足球比分 买大小 倍投方案 稳赚 小长假怎么赚钱 双色球计划王 北京pk10精准计划