6. 模組 (Module)
****************

如果從 Python 直譯器離開後又再次進入，之前（幫函式或變數）做的定義都會
消失。因此，想要寫一些比較長的程式時，你最好使用編輯器來準備要輸入給直
譯器的內容，並且用該檔案來運行它。這就是一個*腳本 (script)*。隨著你的
程式越變越長，你可能會想要把它分開成幾個檔案，讓它比較好維護。你可能也
會想用一個你之前已經在其他程式寫好的函式，但不想要複製該函式的原始定義
到所有使用它的程式裡。

為了支援這一點，Python 有一種方法可以將定義放入檔案中，並在互動模式下
的直譯器中使用它們。這種檔案稱為*模組 (module)*；模組中的定義可以被
*import* 到其他模組中，或是被 *import* 至*主 (main)* 模組（在最頂層執
行的腳本，以及互動模式下，所使用的變數集合）。

模組是指包含 Python 定義和語句的檔案，檔案名稱是模組名稱加上 ".py"。在
模組中，模組的名稱（作為字串）會是全域變數 "__name__" 的值。例如，用你
喜歡的文字編輯器在資料夾中創一個名為 "fibo.py" 的檔案，內容如下：

   # 費波那契數模組

   def fib(n):
       """寫出費波那契數列至第 n 位。"""
       a, b = 0, 1
       while a < n:
           print(a, end=' ')
           a, b = b, a+b
       print()

   def fib2(n):
       """回傳費波那契數列至第 n 位。"""
       result = []
       a, b = 0, 1
       while a < n:
           result.append(a)
           a, b = b, a+b
       return result

現在進入 Python 直譯器並用以下指令 import 這個模組：

   >>> import fibo

這並不會將 "fibo" 中定義的函式名稱直接加入目前的 *namespace* 中（詳情
請見 Python 作用域 (Scope) 及命名空間 (Namespace)）；它只會加入 "fibo"
的模組名稱。使用此模組名稱，就可以存取函式：

   >>> 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'

如果你打算經常使用其中某個函式，可以將其指定至區域變數：

   >>> fib = fibo.fib
   >>> fib(500)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377


6.1. 深入了解模組
=================

模組可以包含可執行的陳述式以及函式的定義。這些陳述式是作為模組的初始化
，它們只會在*第一次*被 import 時才會執行。[1]（如果檔案被當成腳本執行
，也會執行它們）。

每個模組都有它自己的私有命名空間 (namespace)，模組內定義的函式會把該模
組的私有符號表當成全域命名空間使用。因此，模組的作者可以在模組中使用全
域變數，而不必擔心和使用者的全域變數發生意外的名稱衝突。另一方面，如果
你知道自己在做什麼，你可以用這個方式取用模組的全域變數，以和引用函式一
樣的寫法，"modname.itemname"。

在一個模組中可以 import 其他模組。把所有的 "import" 陳述式放在模組（就
這邊來說，腳本也是一樣）的最開頭是個慣例，但並沒有強制。如放置在模組的
最高層（不在任何函式或 class 中），被 import 的模組名稱將被加入全域命
名空間中。

"import" 陳述式有另一種變形寫法，可以直接將名稱從欲 import 的模組，直
接 import 至原模組的命名空間中。例如：

   >>> from fibo import fib, fib2
   >>> fib(500)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

在 import 之後的名稱會被導入，但定義該函式的整個模組名稱並不會被引入在
區域命名空間中（因此，示例中的 "fibo" 未被定義）。

甚至還有另一種變形寫法，可以 import 模組定義的所有名稱：

   >>> from fibo import *
   >>> fib(500)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

這個寫法會 import 模組中所有的名稱，除了使用底線 ("_") 開頭的名稱。大
多數情況下，Python 程式設計師不大使用這個功能，因為它在直譯器中引入了
一組未知的名稱，並且可能覆蓋了某些你已經定義的內容。

請注意，一般情況下並不建議從模組或套件中 import "*" 的做法，因為它通常
會導致可讀性較差的程式碼。但若是使用它來在互動模式中節省打字時間，則是
可以接受的。

如果模組名稱後面出現 "as"，則 "as" 之後的名稱將直接和被 import 模組綁
定在一起。

   >>> import fibo as fib
   >>> fib.fib(500)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

這個 import 方式和 "import fibo" 實質上是一樣的，唯一的差別是現在要用
"fib" 使用模組。

在使用 "from" 時也可以用同樣的方式獲得類似的效果：

   >>> from fibo import fib as fibonacci
   >>> fibonacci(500)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

備註:

  出於效率原因，每個模組在每個直譯器 session 中僅會被 import 一次。因
  此，如果你更改了模組，則必須重啟直譯器——或者，如果只是一個想要在互動
  模式下測試的模組，可以使用 "importlib.reload()"。例如："import
  importlib; importlib.reload(modulename)"。


6.1.1. 把模組當作腳本執行
-------------------------

當使用以下內容運行 Python 模組時：

   python fibo.py <arguments>

如同使用 import 指令，模組中的程式碼會被執行，但 "__name__" 被設為
""__main__""。這意味著，透過在模組的末尾添加以下程式碼：

   if __name__ == "__main__":
       import sys
       fib(int(sys.argv[1]))

你可以將檔案作為腳本也同時可以作為被 import 的模組，因為剖析 (parse)
命令列的程式碼只會在當模組是「主」檔案時，才會執行：

   $ python fibo.py 50
   0 1 1 2 3 5 8 13 21 34

如果此模組是被 import 的，則該段程式碼不會被執行：

   >>> import fibo
   >>>

這通常是用來為模組提供方便的使用者介面，或者用於測試目的（執行測試套件
時，以腳本的方式執行模組）。


6.1.2. 模組的搜尋路徑
---------------------

Import 一個名為 "spam" 的模組時，直譯器首先會搜尋具有該名稱的內建模組
。模組名稱列在 "sys.builtin_module_names" 當中。如果找不到，接下來會在
變數 "sys.path" 所給定的資料夾清單之中，搜尋一個名為 "spam.py" 的檔案
。"sys.path" 從這些位置開始進行初始化：

* 輸入腳本所位在的資料夾（如未指定檔案時，則是目前資料夾）。

* "PYTHONPATH"（一連串和 shell 變數 "PATH" 的語法相同的資料夾名稱）。

* 與安裝相關的預設值（按慣例會包含一個 "site-packages" 資料夾，它是由
  "site" 模組所處理）。

在 sys.path 模組搜尋路徑的初始化 有更多的細節。

備註:

  在支援符號連結 (symlink) 的檔案系統中，輸入腳本的所在資料夾是在跟隨
  符號連結之後才被計算的。換言之，包含符號連結的資料夾**並沒有**增加到
  模組的搜尋路徑中。

初始化之後，Python 程式可以修改 "sys.path"。執行中腳本的所在資料夾會在
搜尋路徑的開頭，在標準函式庫路徑之前。這代表該資料夾中的腳本會優先被載
入，而不是函式庫資料夾中相同名稱的模組。除非是有意要做這樣的替換，否則
這是一個錯誤。 請參見標準模組以瞭解更多資訊。


6.1.3. 「編譯」Python 檔案
--------------------------

為了加快載入模組的速度，Python 將每個模組的編譯版本暫存在
"__pycache__" 資料夾下，並命名為 "module.*version*.pyc"， 這裡的
version 是編譯後的檔案的格式名稱，且名稱通常會包含 Python 的版本編號。
例如，在 CPython 3.3 中，spam.py 的編譯版本將被暫存為
"__pycache__/spam.cpython-33.pyc"。此命名準則可以讓來自不同版本的編譯
模組和 Python 的不同版本同時共存。

Python 根據原始碼最後修改的日期，檢查編譯版本是否過期而需要重新編譯。
這是一個完全自動的過程。另外，編譯後的模組獨立於平台，因此不同架構的作
業系統之間可以共用同一函式庫。

Python 在兩種情況下不檢查快取 (cache)。首先，它總是重新編譯且不儲存直
接從命令列載入的模組的結果。第二，如果沒有源模組，則不會檢查快取。要支
援非源模組（僅編譯）的發布，編譯後的模組必須位於原始資料夾中，並且不能
有源模組。

一些給專家的秘訣：

* 可以在 Python 指令上使用開關參數 (switch) "-O" 或 "-OO" 來減小已編譯
  模組的大小。開關參數 "-O" 刪除 assert（斷言）陳述式，而 "-OO" 同時刪
  除 assert 陳述式和 __doc__ 字串。由於有些程式可能依賴於上述這些內容
  ，因此只有在你知道自己在做什麼時，才應使用此參數。「已最佳化」模組有
  "opt-" 標記，且通常較小。未來的版本可能會改變最佳化的效果。

* 讀取 ".pyc" 檔案時，程式的執行速度並不會比讀取 ".py" 檔案快。唯一比
  較快的地方是載入的速度。

* 模組 "compileall" 可以為資料夾中的所有模組建立 .pyc 檔。

* 更多的細節，包括決策流程圖，請參考**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" 是一個字串 list，它決定直譯器的模組搜尋路徑。它的初始
值為環境變數 "PYTHONPATH" 中提取的預設路徑，或是當 "PYTHONPATH" 未設定
時，從內建預設值提取。你可以用標準的 list 操作修改該變數：

   >>> import sys
   >>> sys.path.append('/ufs/guido/lib/python')


6.3. "dir()" 函式
=================

內建函式 "dir()" 用於找出模組定義的所有名稱。它回傳一個排序後的字串
list：

   >>> 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']

沒有給引數時，"dir()" 列出目前已定義的名稱：

   >>> a = [1, 2, 3, 4, 5]
   >>> import fibo
   >>> fib = fibo.fib
   >>> dir()
   ['__builtins__', '__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. 套件 (Package)
===================

套件是一種使用「點分隔模組名稱」組織 Python 模組命名空間的方法。例如，
模組名稱 "A.B" 表示套件 "A" 中名為 "B" 的子模組。正如模組使用時，不同
模組的作者不需擔心與其他模組的全域變數名稱重複，點分隔模組名稱的使用，
也讓多模組套件（像 NumPy 或 Pillow）的作者們不須擔心其他套件的模組名稱
。

假設你想設計一個能統一處理音訊檔案及音訊數據的模組集（「套件」）。因為
音訊檔案有很多的不同的格式（通常以它們的副檔名來辨識，例如：".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
                 ...

Import 套件時，Python 會搜尋 "sys.path" 裡的目錄，尋找套件的子目錄。

目錄中必須含有 "__init__.py" 檔案，才會被 Pyhon 當成套件（除非有使用
*namespace package*，為一個相對進階的功能）；這樣可以避免一些以常用名
稱命名（例如 "string"）的目錄，無意中隱藏了較晚出現在模組搜尋路徑中的
有效模組。在最簡單的情況，"__init__.py" 可以只是一個空白檔案；但它也可
以執行套件的初始化程式碼，或設置 "__all__" 變數，之後會詳述。

套件使用者可以從套件中 import 個別模組，例如：

   import sound.effects.echo

這樣就載入了子模組 "sound.effects.echo"。引用時必須用它的全名：

   sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

另一種 import 子模組的方法是：

   from sound.effects import echo

這段程式碼一樣可以載入子模組 "echo"，並且不加套件前綴也可以使用，因此
能以如下方式使用：

   echo.echofilter(input, output, delay=0.7, atten=4)

另一種變化是直接 import 所需的函式或變數：

   from sound.effects.echo import echofilter

同樣地，這樣也會載入子模組 "echo"，但它的函式 "echofilter()" 就可以直
接使用：

   echofilter(input, output, delay=0.7, atten=4)

請注意，使用 "from package import item" 時，item 可以是套件的子模組（
或子套件），也可以是套件中被定義的名稱，像是函式、class （類別）或變數
。"import" 陳述式首先測試套件中有沒有定義該 item；如果沒有，則會假設它
是模組，並嘗試載入。如果還是找不到 item，則會引發 "ImportError" 例外。

相反地，使用 "import item.subitem.subsubitem" 語法時，除了最後一項之外
，每一項都必須是套件；最後一項可以是模組或套件，但不能是前一項中定義的
class、函式或變數。


6.4.1. 從套件中 import *
------------------------

當使用者寫下 "from sound.effects import *" 時，會發生什麼事？理想情況
下，我們可能希望程式碼會去檔案系統，尋找套件中存在的子模組，並將它們全
部 import。這會花費較長的時間，且 import 子模組的過程可能會有不必要的
副作用，這些副作用只有在明確地 import 子模組時才會發生。

唯一的解法是由套件作者為套件提供明確的索引。"import" 陳述式使用以下慣
例：如果套件的 "__init__.py" 程式碼有定義一個名為 "__all__" 的 list，
若遇到 "from package import *" 的時候，它就會是要被 import 的模組名稱
。發布套件的新版本時，套件作者可自行決定是否更新此 list。如果套件作者
認為沒有人會從他的套件中 import *，他也可能會決定不支援這個 list。舉例
來說，"sound/effects/__init__.py" 檔案可包含以下程式碼：

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

意思是，"from sound.effects import *" 將會 import "sound.effects" 套件
中，這三個被提名的子模組。

請注意，子模組可能會被區域定義 (locally defined) 的名稱遮蔽。例如，如
果你在 "sound/effects/__init__.py" 檔案中新增了一個 "reverse" 函式，則
"from sound.effects import *" 只會引入兩個子模組 "echo" 和 "surround"
，但*不是* "reverse" 子模組，因為它被區域定義的 "reverse" 函式遮蔽了：

   __all__ = [
       "echo",      # 指向 'echo.py' 檔案
       "surround",  # 指向 'surround.py' 檔案
       "reverse",   # !!! 現在指向 'reverse' 函式 !!!
   ]

   def reverse(msg: str):  #  <-- 在 'from sound.effects import *' 的情況下
       return msg[::-1]    #     這個名稱遮蔽了 'reverse.py' 子模組

如果 "__all__" 沒有被定義，"from sound.effects import *" 陳述式*並不會
*把 "sound.effects" 套件中所有子模組都 import 到目前的命名空間；它只保
證 "sound.effects" 套件有被 import（可能會運行 "__init__.py" 中的初始
化程式碼），然後 import 套件中被定義的全部名稱。這包含 "__init__.py"
定義（以及被明確載入的子模組）的任何名稱。它也包括任何之前被 "import"
陳述式明確載入的套件子模組。請看以下程式碼：

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

此例中，當 "from...import" 陳述式被執行時，"echo" 和 "surround" 模組被
import 進目前的命名空間，因為它們是在 "sound.effects" 套件裡定義的。（
當 "__all__" 有被定義時，這規則也有效。）

雖然，有些特定模組的設計，讓你使用 "import *" 時，該模組只會輸出遵循特
定樣式的名稱，但在正式環境 (production) 的程式碼中這仍然被視為是不良習
慣。

記住，使用 "from package import specific_submodule" 不會有任何問題！實
際上，這是推薦用法，除非 import 的模組需要用到的子模組和其他套件的子模
組同名。


6.4.2. 套件內引用
-----------------

當套件的結構為多個子套件的組合時（如同範例中的 "sound" 套件），可以使
用「絕對 (absolute) import」，引用同層套件中的子模組。例如，要在
"sound.filters.vocoder" 模組中使用 "sound.effects" 中的 "echo" 模組時
，可以用 "from sound.effects import echo"。

你也可以用 "from module import name" 的 import 陳述式，編寫「相對
(relative) import」。這些 import 使用前導句號指示相對 import 中的目前
套件和母套件。例如，在 "urround" 模組中，你可以使用：

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

請注意，相對 import 的運作是以目前模組的套件名稱為依據。因為主模組並沒
有套件，所以如果一個模組預期被用作 Python 應用程式的主模組，那它必須永
遠使用絕對引入。


6.4.3. 多目錄中的套件
---------------------

套件也支援一個特殊屬性 "__path__"。它在初始化時是一個字串的*序列*的
"__init__.py" 檔案所在的目錄名稱，初始化時機是在這個檔案的程式碼被執行
之前。這個變數可以被修改，但這樣做會影響將來對套件內的模組和子套件的搜
尋。

雖然這個特色不太常被需要，但它可用於擴充套件中的模組集合。

-[ 註解 ]-

[1] 實際上，函式定義也是「被執行」的「陳述式」；在執行模組階層的函式定
    義時，會將函式名稱加到模組的全域命名空間。
