本文 AI 產出,尚未審核

Python 模組進階:__all__ 控制匯出


簡介

在大型 Python 專案中,模組與套件的介面設計往往會直接影響程式的可讀性、可維護性與使用者體驗。__all__ 這個特殊變數提供了一種明確宣告「此模組對外公開的名稱」的機制,讓開發者可以在 from module import * 時精準控制哪些物件會被匯入,同時避免不必要的命名衝突與意外暴露實作細節。

即使在日常開發中,我們不常使用 import *,但了解 __all__ 的運作原理仍然很重要,因為它同時影響 IDE 的自動補完、文件產生工具 (如 Sphinx) 以及套件的 __init__.py。掌握這個概念,能讓你在設計公共 API 時更具一致性與可預測性。


核心概念

1. __all__ 是什麼?

  • __all__ 必須是一個 字串列表,列出模組中「允許被外部 import * 匯入」的名稱。
  • 若模組未定義 __all__import * 會匯入 所有不以底線 _ 開頭 的全域名稱。
  • __all__ 不會限制 直接使用 module.namefrom module import name 的行為,只是對 * 匯入提供白名單。

範例

# mymodule.py
__all__ = ['add', 'multiply']

def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

def _private_helper(x):
    return x ** 2

在其他檔案執行 from mymodule import * 時,只會得到 addmultiply_private_helper 不會被匯入。

2. 為什麼要使用 __all__

優點 說明
明確的公共 API 開發者一眼就能看出哪些名稱是「給使用者」的,哪些是內部實作。
防止意外衝突 避免在大型專案中因 import * 把不相關變數帶進來而產生名稱覆寫。
文件產生工具支援 Sphinx、pydoc 等工具會根據 __all__ 產生更乾淨的文件。
提升載入效能 雖然影響不大,但 import * 時只載入列在 __all__ 的物件,可減少不必要的名稱解析。

3. __all__ 的基本語法

# module.py
__all__ = ['ClassA', 'func_b', 'CONST_VALUE']
  • 字串 必須是 有效的 Python 標識符,且必須在模組的全域範圍中已經定義。
  • __all__ 可以在模組的任何位置 定義,只要在執行 import * 前已經被解釋器評估即可。

4. 進階範例

4.1 動態生成 __all__

在某些情況下,我們希望根據條件自動決定要匯出的名稱,例如根據環境變數或是可用的第三方套件:

# dynamic_all.py
import os

__all__ = ['base_func']

def base_func():
    return "base"

if os.getenv('ENABLE_EXTRA'):
    __all__.append('extra_func')

    def extra_func():
        return "extra feature enabled"

說明:當環境變數 ENABLE_EXTRA 被設定時,extra_func 會被加入 __all__,否則不會被匯出。

4.2 在套件的 __init__.py 中使用 __all__

套件根目錄的 __init__.py 常常負責重新導出子模組的公共 API:

# mypackage/__init__.py
from .core import CoreClass, core_helper
from .utils import util_func

__all__ = ['CoreClass', 'core_helper', 'util_func']

好處:使用者只需要 import mypackage 即可取得所有重要功能,而不必關心子模組的實際結構。

4.3 防止意外匯入第三方模組

# safe_import.py
import json   # 標準庫
import yaml   # 可能未安裝

__all__ = ['load_json']

def load_json(text: str):
    return json.loads(text)

def load_yaml(text: str):
    # 只在需要時才匯入,避免在 import 時拋出 ImportError
    import yaml
    return yaml.safe_load(text)

技巧load_yaml 沒有被放入 __all__,因此 from safe_import import * 不會把它匯入,使用者若需要 YAML 功能必須明確匯入。

4.4 使用 __all__ 隱藏測試函式

在開發階段,我們常在模組底部放置測試程式碼:

# module_with_tests.py
def public_api():
    return "public"

def _internal():
    return "internal"

def _test():
    assert public_api() == "public"
    assert _internal() == "internal"

if __name__ == '__main__':
    _test()

__all__ = ['public_api']

重點:即使 _test 在模組內部可被呼叫,__all__ 仍保證它不會被 import * 匯出。


常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記更新 __all__ 新增或移除函式後忘記同步 __all__,導致使用者無法匯入或意外匯入舊名稱。 在 CI / lint 工具加入 flake8F822 檢查,或使用 pylintredefined-builtin
使用 import * 在套件內部 在套件的 __init__.py 內部使用 from .module import *,容易把不該公開的名稱帶出。 盡量避免 import *,改用明確的 from .module import ClassA, func_b
把變數放入 __all__ 卻未定義 列在 __all__ 卻沒有相應的變數,會在 import * 時拋出 AttributeError 在模組最後檢查 __all__,或使用 assert all(hasattr(sys.modules[__name__], name) for name in __all__) 進行測試。
混用底線命名與 __all__ 依賴「以 _ 開頭自動隱藏」的慣例,同時又手動列出同名項目,造成混亂。 統一規則:若要公開,不要使用底線;若是私有,不要列入 __all__
動態生成 __all__ 時的可讀性 依條件改變 __all__ 會讓文件變得難以追蹤。 在文件頂部加入說明註解,或將動態邏輯抽成函式 def _build_all(): ...,最後 __all__ = _build_all()

最佳實踐

  1. 明確列出所有公開名稱:即使只有一兩個,也請寫 __all__
  2. 保持 __all__ 與實際實作同步:使用自動化測試或 lint 工具檢查。
  3. 避免在套件根目錄使用 import *:改用顯式匯入,提升可讀性。
  4. 將私有函式、類別名稱以單底線 _ 開頭,並確保不在 __all__ 中。
  5. 文件化公共 API:在 README 或 docstring 中說明 __all__ 的意圖,讓使用者清楚哪些是正式支援的介面。

實際應用場景

1. 公共套件的 API 設計

假設你在開發一個資料分析套件 datakit,裡面有大量的內部工具函式。透過 __all__,你可以只向使用者暴露「核心」功能:

# datakit/__init__.py
from .io import read_csv, write_csv
from .stats import mean, median, std
from ._internal import _cache, _debug

__all__ = ['read_csv', 'write_csv', 'mean', 'median', 'std']

使用者只需要 import datakit as dk,即可直接呼叫 dk.mean(),而不必擔心 _cache 之類的內部實作被誤用。

2. 多平台套件的條件匯出

在跨平台套件(Windows、Linux、macOS)中,某些功能只在特定系統可用。利用 __all__ 動態調整匯出列表,可避免在不支援的環境下產生 ImportError

# platform_tools.py
import sys

__all__ = ['common_util']

def common_util():
    return "works everywhere"

if sys.platform.startswith('win'):
    __all__.append('win_specific')
    def win_specific():
        return "Windows only"
elif sys.platform.startswith('linux'):
    __all__.append('linux_specific')
    def linux_specific():
        return "Linux only"

3. 教學或範例程式的「乾淨」匯入

在教學文件中,我們常希望使用者只看到與範例相關的名稱,避免雜訊:

# tutorial_example.py
def foo():
    return "foo"

def _helper():
    return "hidden"

__all__ = ['foo']

讀者在 from tutorial_example import * 時,只會得到 foo,不會被 _helper 混淆。


總結

  • __all__ 是 Python 模組層級的「公開 API 白名單」,專門用於控制 from module import * 時的匯出行為。
  • 它不會限制直接 import modulefrom module import name,但能讓程式碼的 可讀性、文件產出與命名衝突管理 更加一致。
  • 透過 明確列出公開名稱、配合底線私有慣例、使用自動化檢查,可以避免常見的陷阱。
  • 套件設計、跨平台條件匯出、教學範例 等實務情境中,__all__ 都是讓 API 更乾淨、使用者更友善的關鍵工具。

掌握 __all__ 後,你的模組將不再是「一團亂碼」的集合,而是 有條理、可預測且易於維護 的程式庫。祝你在 Python 的模組設計之路上越走越順!