Skip to content

Python 模組與套件 (Module & Package)

在 Python 的程式設計旅程中,當專案規模逐漸擴大,程式碼的組織與管理就變得至關重要。將所有邏輯塞在單一 .py 檔案中,不僅難以閱讀,更不利於維護與協作。為了解決這個問題,Python 提供了強大的模組 (Module)套件 (Package) 機制,讓我們能夠以更結構化、更模組化的方式開發應用程式。本文將深入探討這些概念,並引導您掌握其精髓。

📦 模組 (Module):程式碼的基石

在 Python 中,一個 .py 檔案就可以被視為一個模組。模組能讓你將相關的程式碼(如函數、類別、變量)組織在一起,方便重複使用並保持程式碼的清晰度。

1. 建立與使用模組

假設我們有一個專案,需要一些算術運算輔助函數。我們可以建立一個 arithmetic_ops.py 檔案:

python
# arithmetic_ops.py
PI_CONSTANT = 3.1415926535

def sum_numbers(a, b):
    """計算兩數之和"""
    return a + b

def subtract_numbers(a, b):
    """計算兩數之差"""
    return a - b

class GeometryCircle:
    """圓形幾何類別"""
    def __init__(self, radius):
        self.radius = radius

    def calculate_area(self):
        """計算圓形面積"""
        return PI_CONSTANT * self.radius ** 2

現在,我們可以在同一個目錄下的另一個檔案 main_app.py 中使用這個模組:

python
# main_app.py
import arithmetic_ops

result_sum = arithmetic_ops.sum_numbers(10, 5)
print(f"10 + 5 = {result_sum}") # 輸出: 10 + 5 = 15

circle_instance = arithmetic_ops.GeometryCircle(10)
print(f"半徑為 10 的圓面積: {circle_instance.calculate_area()}") # 輸出: 半徑為 10 的圓面積: 314.15926535
print(f"PI 的值: {arithmetic_ops.PI_CONSTANT}") # 輸出: PI 的值: 3.1415926535

2. 多樣化的導入 (Import) 方式

Python 提供了多種導入模組或其內容的方式:

a. import module_name

這是最基本的導入方式。它會導入整個模組,你需要透過 module_name.item 來存取模組內的成員。

python
import math # 內建模組

print(math.sqrt(16)) # 輸出: 4.0

b. from module_name import item1, item2, ...

如果你只需要模組中的特定項目,可以使用這種方式。導入後,你可以直接使用項目名稱,無需模組名稱前綴。

python
from arithmetic_ops import sum_numbers, GeometryCircle

# diff_result = subtract_numbers(20, 7) # 錯誤! subtract_numbers 未被導入
result_sum_again = sum_numbers(100, 50)
print(f"100 + 50 = {result_sum_again}") # 輸出: 100 + 50 = 150

my_circle_instance = GeometryCircle(5)
print(f"半徑為 5 的圓面積: {my_circle_instance.calculate_area()}")

c. from module_name import * (不建議)

這種方式會導入模組中的所有公開名稱(非底線開頭的名稱,或 __all__ 列表指定的名稱)。雖然方便,但強烈不建議在大型專案中使用,因為它容易造成命名空間污染 (namespace pollution),使得程式碼難以追蹤變量或函數的來源,也可能覆蓋現有的名稱。

python
# 假設 arithmetic_ops.py 中定義了 __all__ = ["sum_numbers", "PI_CONSTANT"]
# from arithmetic_ops import * (假設 __all__ 如上設定)

# print(sum_numbers(1, 2))
# print(PI_CONSTANT)
# print(subtract_numbers(3,1)) # NameError: name 'subtract_numbers' is not defined (如果 __all__ 限制了)
# my_geo_circle = GeometryCircle(2) # NameError: name 'GeometryCircle' is not defined (如果 __all__ 限制了)

d. 使用別名 (Alias)

當模組名稱過長,或想避免與現有名稱衝突時,可以使用 as 關鍵字為模組或導入的項目設定別名。

python
import arithmetic_ops as ao
from arithmetic_ops import subtract_numbers as sub_nums

sum_val = ao.sum_numbers(5, 3)
print(f"5 + 3 = {sum_val}") # 輸出: 5 + 3 = 8

diff_val = sub_nums(10, 4)
print(f"10 - 4 = {diff_val}") # 輸出: 10 - 4 = 6

3. __name__ 屬性與腳本執行

每個模組都有一個內建的 __name__ 屬性。當模組被直接執行時 (例如 python arithmetic_ops.py),其 __name__ 的值為 "__main__"。當模組被其他模組導入時,__name__ 的值則是該模組的名稱 (例如 "arithmetic_ops")。

這個特性常用來在模組中編寫僅供測試或直接執行時才運行的程式碼:

python
# arithmetic_ops.py (續)

# ... (之前的定義) ...

if __name__ == "__main__":
    # 這段程式碼只會在直接執行 arithmetic_ops.py 時運行
    print("arithmetic_ops.py 被直接執行了!")
    test_sum = sum_numbers(1, 1)
    print(f"測試 sum_numbers(1,1): {test_sum}")
    test_circle_instance = GeometryCircle(2)
    print(f"測試 GeometryCircle(2) 面積: {test_circle_instance.calculate_area()}")

# 當 main_app.py 執行 `import arithmetic_ops` 時,以上 if 區塊內的程式碼不會執行。

📁 套件 (Package):模組的組織者

當專案變得更複雜,擁有多個模組時,我們可以將相關模組組織成一個套件。套件本質上是一個包含目錄結構的模組集合。

__init__.py 的角色

對於一般套件 (Regular Packages)__init__.py 檔案扮演著關鍵角色:

  1. 標識套件:它的存在使 Python 將一個目錄視為一般套件。
  2. 套件初始化:你可以在 __init__.py 中執行套件級別的 initialization code。例如,設定一些套件級別的變量。
  3. 簡化導入:可以利用 __init__.py 將子模組或子套件中的特定名稱提升到套件的命名空間中,使得導入更方便。

例如,一個財務儀表板應用的 report_generator 套件結構:

finance_dashboard/
├── dashboard_app.py
└── report_generator/           # 一般套件
    ├── __init__.py
    ├── data_processing/        # 子套件 (也是一般套件)
    │   ├── __init__.py
    │   └── text_cleaner.py
    └── data_visuals/           # 子套件 (也是一般套件)
        ├── __init__.py
        └── chart_plotter.py

report_generator/data_processing/__init__.py 中:

python
# report_generator/data_processing/__init__.py
from .text_cleaner import sanitize_report_title, tokenize_content

print("Data Processing sub-package initialized!")

然後在 dashboard_app.py 中可以這樣導入:

python
# dashboard_app.py
from report_generator.data_processing import sanitize_report_title
print(sanitize_report_title(" test "))
  1. 控制 from package import * 的行為:透過在 __init__.py 中定義 __all__ 列表 (一個包含字串的列表,字串是公開模組的名稱),可以明確指定當使用者執行 from report_generator import * 時,哪些模組或名稱應該被導入。

⚖️ 套件類型:一般套件 vs 命名空間套件

Python 套件主要有兩種形式:一般套件和命名空間套件。了解它們的區別以及如何混合使用,對於設計大型或可擴展的應用程式非常重要。

1. 一般套件 (Regular Packages)

  • 定義:一個包含 __init__.py 檔案的目錄。這是 Python 傳統的套件形式。
  • 行為__init__.py 的存在告訴 Python 這是一個一般套件。此檔案可以為空,也可以包含初始化程式碼或定義 __all__
  • 結構:一般套件的內容通常集中在單一的目錄樹下。

2. 命名空間套件 (Namespace Packages)

  • 定義:一個或多個目錄,它們共享相同的頂層套件名稱,但這些目錄本身不包含 __init__.py 檔案 (PEP 420)。
  • 行為:Python 在導入時會掃描 sys.path 上的所有路徑。如果多個路徑下都存在同名且沒有 __init__.py 的目錄,它們的內容會被合併成一個單一的命名空間套件。
  • 核心優勢
    • 可擴展性與模組化 (Extensibility and Modularity):允許將一個大型套件分割成多個獨立開發、獨立部署的部分。不同的團隊或專案可以貢獻到同一個命名空間,而無需中心化的協調或修改單一的 __init__.py
    • 插件架構 (Plugin Architectures):非常適合實現插件系統。核心應用程式可以定義一個命名空間,而插件可以作為該命名空間下的獨立套件安裝,自動被發現和集成。
    • 避免命名衝突 (Avoiding Name Clashes):當多個大型函式庫可能希望使用相同的頂層套件名(例如 utils)時,命名空間套件允許它們共存並提供各自的功能,而不會互相覆蓋。
    • 漸進式功能提供 (Incremental Feature Provision):可以發佈一個核心套件,然後將額外的、可選的功能作為同一命名空間下的附加套件提供。用戶只需安裝他們需要的部分。

3. 混合使用一般套件與命名空間套件

一個複雜的專案結構中,完全可以混合使用一般套件和命名空間套件

  • 一個頂層的命名空間套件可以包含多個子套件,其中一些子套件可以是傳統的帶有 __init__.py 的一般套件,而另一些則可以是該命名空間套件的進一步擴展部分(即沒有 __init__.py 的目錄)。
  • 反之,一個一般套件(有 __init__.py)也可以包含子目錄,這些子目錄如果沒有 __init__.py,則不能作為該一般套件的子命名空間套件被自動發現和合併(它們僅僅是普通目錄,除非其完整路徑被添加到 sys.path 並被視為獨立的命名空間套件入口)。

範例:混合結構

假設我們有一個大型企業應用 enterprise_suite,它希望提供一個核心框架,並允許不同的部門(如 saleshr)獨立開發和部署它們的模組。

# 假設以下路徑都在 sys.path 中
path_to_core_framework/
└── enterprise_suite/           # 頂層 enterprise_suite 可以是一個命名空間套件 (無 __init__.py)
    └── core/                   # core 可以是一個一般套件,提供核心功能
        ├── __init__.py
        └── base_models.py

path_to_sales_module/
└── enterprise_suite/           # sales 模組貢獻到 enterprise_suite 命名空間
    └── sales/                  # sales 自身也可以是一個一般套件
        ├── __init__.py
        └── crm_connector.py

path_to_hr_module/
└── enterprise_suite/           # hr 模組也貢獻到 enterprise_suite 命名空間
    └── hr/                     # hr 的特定工具,可以是命名空間套件的一部分 (無 __init__.py)
        └── payroll_utils.py    # 如果 hr 目錄沒有 __init__.py
        # 或者 hr 也可以是一個一般套件
        # ├── __init__.py
        # └── payroll_utils.py

在這個例子中:

  1. enterprise_suite 作為一個頂層命名空間套件,允許 coresaleshr 等不同來源的模組整合進來。
  2. enterprise_suite.core 是一個一般套件,有自己的 __init__.py 和模組。
  3. enterprise_suite.sales 也是一個一般套件,貢獻到 enterprise_suite 命名空間下。
  4. enterprise_suite.hr 可以是一個一般套件 (如果有 __init__.py),也可以是 enterprise_suite 命名空間套件的直接一部分 (如果 hr 目錄下沒有 __init__.py,且 enterprise_suite 本身是命名空間套件)。

導入方式

python
import enterprise_suite.core.base_models
import enterprise_suite.sales.crm_connector
import enterprise_suite.hr.payroll_utils

# 使用來自不同部分的模組
user_model = enterprise_suite.core.base_models.User()
lead_info = enterprise_suite.sales.crm_connector.get_lead("some_id")
salary_data = enterprise_suite.hr.payroll_utils.calculate_salary("employee_id")

這種混合能力使得 Python 的套件系統非常靈活,能夠適應從小型腳本到大型企業級應用的各種需求。使用命名空間套件的關鍵在於理解其「合併」行為和對 __init__.py 的省略。

🔁 相對導入 (Relative Import) vs 絕對導入 (Absolute Import)

在套件內部,模組之間需要互相導入時,有兩種主要方式:

1. 絕對導入 (Absolute Import)

絕對導入是從專案的根路徑(或者說,sys.path 中可找到的頂層套件)開始指定完整的導入路徑。

python
# 假設在 report_generator/data_visuals/chart_plotter.py 中需要用到 text_cleaner
# 絕對導入:
from report_generator.data_processing.text_cleaner import sanitize_report_title

class EnhancedBarChart(BarChart): # 繼承之前的 BarChart
    def __init__(self, data_points, category_labels, chart_title):
        super().__init__(data_points, category_labels)
        self.title = sanitize_report_title(chart_title) # 使用導入的函數

    def get_chart_description(self):
        base_desc = super().get_chart_description()
        return f"Title: {self.title}\n{base_desc}"

優點:

  • 清晰明確:路徑一目了然,不會有歧義。
  • 易於重構:如果移動了當前模組,只要導入路徑仍然有效,就不需要修改。
  • PEP 8 推薦使用絕對導入。

缺點:

  • 如果套件名稱很長或嵌套很深,導入語句可能會變得很冗長。

2. 相對導入 (Relative Import)

相對導入是基於當前模組的位置,使用 . (表示當前目錄) 和 .. (表示上層目錄) 來指定導入路徑。相對導入只能用於套件內部的模組之間,不能用於執行頂層腳本。

python
# 假設在 report_generator/data_visuals/chart_plotter.py 中:
# from . import some_sibling_in_data_visuals  # 導入同目錄下的 some_sibling_in_data_visuals.py
# from ..data_processing import text_cleaner # 導入上層目錄(report_generator)中的 data_processing 子套件下的 text_cleaner.py

# 在 report_generator/data_visuals/chart_plotter.py 中需要用到 text_cleaner
# 相對導入:
from ..data_processing.text_cleaner import sanitize_report_title # .. 代表 data_visuals 的上一層,即 report_generator

class AnotherChart:
    def __init__(self, title):
        self.title = sanitize_report_title(title)
    # ...

. 的意義:

  • from .module import item: 從當前套件(與當前文件同目錄的 __init__.py 所在的套件,或當前命名空間套件部分)導入 module
  • from ..subpackage import item: 從父套件導入 subpackage

優點:

  • 簡潔:對於深層嵌套的套件,導入語句可以更短。
  • 套件重命名友好:如果整個頂層套件被重命名,相對導入語句不需要修改。

缺點:

  • 可讀性稍差:有時不容易一眼看出實際導入的路徑。
  • 限制:不能在作為腳本直接運行的模組中使用(即 __name__ == "__main__" 的模組)。

3. 常見的導入錯誤

a. ModuleNotFoundError: No module named 'X'

  • 原因:Python 解釋器在 sys.path(一個包含搜尋路徑的列表)中找不到名為 X 的模組或套件。
  • 解決
    1. 確認模組/套件名稱拼寫正確。
    2. 確認檔案或目錄實際存在。
    3. 確認包含該模組/套件的目錄在 sys.path 中。通常,執行腳本所在的目錄及其父目錄(如果是作為套件的一部分運行)會被自動加入。
    4. 如果是第三方套件,確認已經使用 pip install X 安裝。
    5. 檢查虛擬環境是否啟動。

b. ImportError: attempted relative import with no known parent package

  • 原因:當你直接執行一個包含相對導入的 Python 檔案時 (例如 python report_generator/data_visuals/chart_plotter.py),Python 不知道這個檔案屬於哪個套件,因為它被當作頂層腳本執行,__name__ "__main__",而不是套件內部的模組名。相對導入需要知道「父套件」是什麼。
  • 解決
    1. 不要直接執行套件內的模組。應該從專案的根目錄 (例如 finance_dashboard/) 執行一個入口腳本 (如 dashboard_app.py),該腳本使用絕對導入或正確的相對導入來調用套件內的功能。
    2. 如果需要測試套件內的模組,可以使用 python -m report_generator.data_visuals.chart_plotter 的方式執行。-m 參數告訴 Python 將該檔案作為一個模組來執行,這樣 Python 就能正確解析套件結構和相對導入。

c. ImportError: cannot import name 'X' from partially initialized module 'Y' (most likely due to a circular import)

  • 原因:循環導入。模組 A 導入模組 B,而模組 B 又(直接或間接)導入模組 A。當 Python 嘗試加載其中一個模組時,它發現需要另一個尚未完全加載的模組,導致錯誤。
  • 解決
    1. 重構程式碼:將共享的依賴提取到第三個模組中,或者重新設計模組職責以打破循環。
    2. 延遲導入:只在函數或方法內部進行導入,而不是在模組的頂層。這樣導入操作會推遲到函數實際被調用時才執行,可能可以避開循環。
      python
      # service_alpha.py
      # from service_beta import beta_operation # 潛在循環
      
      def alpha_operation():
          from service_beta import beta_operation # 延遲導入
          print("Calling beta_operation from alpha_operation")
          beta_operation()
          return "alpha_operation result"
      
      # service_beta.py
      # from service_alpha import alpha_operation # 潛在循環
      
      def beta_operation():
          # 為了避免在這個簡化範例中無限遞歸,我們不在 beta_operation 裡回調 alpha_operation
          # 但實際的循環導入問題可能更複雜
          print("beta_operation called")
          return "beta_operation result"
      
      def another_beta_func_using_alpha():
          from service_alpha import alpha_operation # 延遲導入
          print("Calling alpha_operation from another_beta_func_using_alpha")
          alpha_operation()
      
      # if __name__ == "__main__": # 測試時需注意避免直接觸發未解決的循環
      #     alpha_operation()
    3. __init__.py 中謹慎導入:如果循環發生在套件的 __init__.py 和其子模組之間,要特別小心。

🌍 使用外部套件 (External Packages)

Python 的強大生態系得益於大量的第三方套件。pip (Python Package Installer) 是管理這些套件的標準工具。

1. 安裝套件

bash
pip install requests  # 安裝 requests 套件
pip install numpy pandas # 一次安裝多個套件
pip install "requests>=2.25.1" # 指定版本 (使用真實套件和版本範例)
pip install "matplotlib==3.5.0" # 指定確切版本

2. 管理專案依賴:虛擬環境與 requirements.txt

為每個專案建立獨立的虛擬環境 (Virtual Environment) 是一個非常好的習慣。這可以避免不同專案間的套件版本衝突。

bash
# 建立虛擬環境 (Python 3.3+)
python -m venv my_project_env # 或者 .venv

# 啟動虛擬環境
# Windows:
# my_project_env\Scripts\activate.bat  (或 .ps1 for PowerShell)
# macOS/Linux:
# source my_project_env/bin/activate

# 在虛擬環境中安裝套件
pip install flask beautifulsoup4

# 產生 requirements.txt 檔案,記錄目前環境的依賴
pip freeze > requirements.txt

# 在另一個地方或給其他人使用時,可以根據 requirements.txt 安裝所有依賴
pip install -r requirements.txt

# 停用虛擬環境
deactivate

3. 在程式碼中使用已安裝的套件

一旦安裝完成,就可以像使用內建模組一樣導入和使用它們:

python
import requests # 假設已 pip install requests

try:
    response = requests.get('https://api.github.com/users/python')
    response.raise_for_status() # 如果 HTTP 請求返回了不成功的狀態碼,會拋出 HTTPError 異常
    user_data = response.json()
    print(f"Python GitHub User ({user_data['login']}) Blog: {user_data['blog']}")
except requests.exceptions.RequestException as e:
    print(f"請求錯誤: {e}")

🛠️ 建構自己的套件以供分發

如果你開發了一個實用的模組集合,並希望分享給他人或在多個專案中使用,你可以將其打包成可分發的套件。現代 Python 套件的建構通常依賴 pyproject.toml 檔案和建構工具如 build

1. 專案結構建議

一個名為 strtool 的字串工具套件的典型可分發結構可能如下:

strtool_project/
├── pyproject.toml         # 建構系統配置、專案元數據
├── README.md              # 套件說明
├── LICENSE                # 授權條款
├── src/                   # 源代碼目錄 (推薦的佈局)
│   └── strtool/           # 你的套件實際名稱 (一般套件)
│       ├── __init__.py
│       ├── case_changer.py
│       └── analyzers.py
└── tests/                 # 測試程式碼
    ├── test_case_changer.py
    └── test_analyzers.py

將源代碼放在 src/ 目錄下是一種常見的最佳實踐,可以避免一些導入問題和意外的行為。

2. pyproject.toml 檔案

這是現代 Python 套件打包的核心設定檔,使用 TOML 格式。

toml
# pyproject.toml (for strtool_project)

[build-system]
requires = ["setuptools>=61.0", "wheel"] # 指定建構此套件所需的依賴
build-backend = "setuptools.build_meta"  # 指定建構後端
backend-path = ["."] # 可選

[project]
name = "strtool" # 套件名稱,在 PyPI 上必須唯一
version = "0.1.0" # 套件版本
authors = [
  { name="Your Name", email="you@example.com" },
]
description = "一個實用的 Python 字串處理工具庫。"
readme = "README.md" # 指向 README 檔案
requires-python = ">=3.7" # 最低 Python 版本要求
license = { file="LICENSE" } # 或 {text = "MIT License"}
keywords = ["string", "utility", "text processing", "case conversion"]
classifiers = [ # PyPI 分類器,幫助使用者尋找套件
    "Development Status :: 4 - Beta", # 例如:3 - Alpha, 4 - Beta, 5 - Production/Stable
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License", # 根據你的授權選擇
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.7", # 你明確支援的最低版本
    "Operating System :: OS Independent", # 如果你的套件不依賴特定作業系統
    "Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [ # 執行時期依賴 (strtool 可能沒有,但作為範例)
    # "regex>=2022.1.18",
    # "another_package; python_version<'3.8'", # 條件依賴
]

[project.urls] # 可選,專案相關連結
"Homepage" = "https://github.com/yourusername/strtool"
"Bug Tracker" = "https://github.com/yourusername/strtool/issues"
"Documentation" = "https://yourusername.github.io/strtool" # 範例

# 如果套件提供命令列工具
[project.scripts]
strtool-cli = "strtool.cli:main" # 假設 strtool/cli.py 有個 main() 函數

# 告訴 setuptools 在哪裡找到你的套件
[tool.setuptools.packages.find]
where = ["src"] # 套件源代碼位於 src 目錄下
# 若 src/strtool 是一個命名空間套件 (即 src/strtool 沒有 __init__.py)
# 且你想讓 setuptools 將其識別為命名空間套件,可以加上:
# namespaces = true

3. setup.pysetup.cfg (現代觀點)

在過去,setup.py 是建構套件的核心檔案,有時會搭配 setup.cfg 使用。隨著 pyproject.toml (PEP 517, PEP 518, PEP 621) 的引入和 setuptools (版本 61+) 等建構後端的發展,對於許多純 Python 套件而言,setup.py 檔案已不再是必需的

  • pyproject.toml 優先:所有專案元數據 (如名稱、版本、依賴) 和建構系統需求都應在 pyproject.toml 中聲明。
  • setup.cfg 的式微:雖然 setup.cfg 仍可與 pyproject.toml 搭配使用,將一些 setuptools 特定選項放在其中,但趨勢是將盡可能多的配置整合到 pyproject.toml
  • setup.py 的角色轉變:如果你的專案使用了 setuptools 作為建構後端 (如上 pyproject.toml 所示),並且沒有複雜的 C 擴展或需要在建構時執行任意 Python 程式碼的特殊需求,你通常不需要 setup.py。如果因為某些舊工具的兼容性或非常特定的進階用途而需要 setup.py,它通常只包含一個最小的樣板,例如 from setuptools import setup; setup(),實際配置仍在 pyproject.tomlsetup.cfg

對於新的純 Python 套件,建議盡量只使用 pyproject.toml 來進行聲明式配置。

4. 建構套件

首先,安裝建構工具:

bash
python -m pip install build

然後在專案根目錄 (包含 pyproject.toml 的目錄,例如 strtool_project/) 執行:

bash
python -m build

這會在專案根目錄下產生一個 dist/ 資料夾,裡面包含兩種格式的套件檔案:

  • .tar.gz: 源代碼分發包 (Source Distribution, sdist)。
  • .whl: Wheel 檔案,預編譯的二進制分發包 (Built Distribution),安裝速度更快。

🌐 PyPI 分發

PyPI (Python Package Index) 是 Python 套件的官方倉庫。一旦你建構好你的套件,就可以將其上傳到 PyPI,讓全世界的 Python 開發者都能安裝和使用。

1. 準備工作

  • 註冊帳號:在 PyPI 上註冊一個帳號。
  • 產生 API Token:登入 PyPI 後,前往你的帳號設定頁面,產生一個新的 API Token。你可以為 Token 設定範圍 (Scope),例如限制它只能上傳到特定的專案。

    重要提示:保存你的 API Token

    API Token 在產生時只會顯示一次!請務必立即將其複製並保存在安全的地方。如果遺失,你將需要重新產生一個新的 Token。

  • 安裝 twinetwine 是一個用於安全地上傳套件到 PyPI 的工具。
    bash
    python -m pip install twine

2. 上傳到 PyPI

在你建構好套件 (即 dist/ 目錄下有 .tar.gz.whl 檔案) 後,執行以下命令:

bash
python -m twine upload dist/*

twine 提示輸入用戶名時,輸入 __token__。 當提示輸入密碼時,貼上你之前產生並保存的 API Token。

Enter your username: __token__
Enter your password: <貼上你的 API Token pypi-AgEIcH...>

twine 會使用這個 Token 安全地將你的套件上傳到 PyPI。

一旦上傳成功,其他人就可以使用 pip install your-package-name (例如 pip install strtool) 來安裝你的套件了。

💡 其他相關概念與最佳實踐

  1. sys.path:Python 解釋器在導入模組時会搜尋的一個路徑列表。它通常包含:

    • 腳本所在的目錄(或當前目錄,如果以互動模式運行)。
    • PYTHONPATH 環境變量中列出的目錄。
    • Python 安裝時的標準庫路徑。
    • site-packages 目錄,第三方套件安裝的地方。 你可以 import sys; print(sys.path) 來查看。
  2. PYTHONPATH 環境變量:一個可選的環境變量,用於告訢 Python 解釋器額外的模組搜尋路徑,其格式與系統的 PATH 環境變量類似。

  3. 文檔字符串 (Docstrings):為你的模組、函數、類別和方法編寫清晰的文檔字符串是個好習慣。它們不僅能幫助他人理解你的程式碼,也能被自動化文件工具 (如 Sphinx) 提取。

  4. 可執行套件 (python -m <package_name>):如果你的套件需要在命令列中作為一個應用程式執行(而非僅作為庫導入),可以在套件目錄 (例如 src/strtool/) 中加入 __main__.py 檔案。當你執行 python -m strtool 時,strtool/__main__.py 檔案會被執行。

    src/strtool/__main__.py:

    python
    # src/strtool/__main__.py
    from .cli import main # 假設 strtool/cli.py 有個 main() 函數
    
    if __name__ == "__main__":
        # 這裡的程式碼會在 python -m strtool 時執行
        # 通常,它會調用一個定義好的入口函數
        print(f"Executing strtool package directly via -m flag...")
        main()

    注意:如果你在 pyproject.toml 中定義了 [project.scripts],例如 strtool-cli = "strtool.cli:main",那麼安裝套件後,使用者可以直接執行 strtool-cli 命令,這通常是提供命令列工具的更常見方式。__main__.py 則提供了 python -m strtool 的執行方式。

  5. 編輯器與 IDE 的支援:現代的 Python IDE (如 VS Code, PyCharm) 對模組和套件有很好的支援,包括自動完成、路徑解析、重構工具等,善用這些工具可以大幅提升開發效率。

🔚 結語

掌握 Python 的模組與套件系統是從初學者邁向進階開發者的關鍵一步。它們不僅能讓你的程式碼更有條理、易於維護,更是參與大型專案、使用第三方函式庫以及貢獻開源社群的基礎。

從建立簡單的模組開始,逐步嘗試組織成一般套件,並在需要高度模組化和可擴展性時,探索命名空間套件及其混合使用的威力。學習如何管理依賴和發佈自己的作品,這個過程將大大提升你的 Python 程式設計能力。持續練習,探索 Python 生態系中豐富的套件,你將能建構出更強大、更優雅的應用程式。


📌 延伸閱讀

KF Software House