返回文章列表

Python套件開發

本文探討 Python 套件開發流程,涵蓋從程式碼撰寫、目錄結構建立、版本控制到封裝測試的完整步驟。使用 cookiecutter 建立套件範本,並以 pycounts 套件為例,示範如何使用 poetry 管理相依性、進行單元測試,以及封裝釋出套件。同時,文章也提供 Docker

Python 軟體開發

Python 的套件機制讓程式碼得以重複利用和分享。本文將逐步示範如何開發一個名為 pycounts 的 Python 套件,用於計算文字檔案中的單詞數量。首先,我們會利用 Python 內建的 Counter 物件來計算單詞出現頻率,並將其封裝成函式。接著,利用 cookiecutter 工具快速建立符合 Python 標準的套件目錄結構,並將程式碼整合進去。為了確保程式碼品質,我們會使用 conda 建立虛擬環境,並使用 poetry 管理套件相依性及執行測試。最後,文章也簡述瞭如何使用 Docker 容器進行環境管理,提升開發效率。

如何包裝一個Python套件

在本章中,我們將從頭到尾開發一個完整的Python套件範例,以展示開發套件的關鍵步驟。本章是本文的基礎,包含了建立Python套件所需的一切知識,並可在未來建立套件時作為參考表。後續章節將進一步詳細探討包裝過程中的每個步驟。

我們在本章中要建立的範例套件將幫助我們計算文字檔案中的單詞數量。我們將其稱為pycounts,它將用於計算諸如小說、研究論文、新聞文章、日誌檔案等文字中的單詞使用情況。

計算文字檔案中的單詞數量

開發我們的程式碼

在考慮製作套件之前,我們首先開發要包裝的程式碼。我們要建立的pycounts套件將幫助我們計算文字檔案中的單詞數量。Python有一個有用的Counter物件,可以用來計算集合中元素的數量(如單詞列表)並將它們儲存在字典中。

我們可以透過首先在命令列中輸入python來開啟Python直譯器來演示Counter的功能:

$ python

然後,我們可以從collections模組匯入Counter類別:

>>> from collections import Counter

現在,我們將定義並使用一個示例單詞列表來建立一個Counter物件:

>>> words = ["a", "happy", "hello", "a", "world", "happy"]
>>> word_counts = Counter(words)
>>> word_counts
Counter({'a': 2, 'happy': 2, 'hello': 1, 'world': 1})

注意Counter物件如何自動計算輸入列表中每個唯一單詞的數量,並將結果作為'word': count對的字典傳回!

將程式碼轉換為函式

在3.1.1節中,我們開發了一個用於計算文字檔案中單詞數量的工作流程。但是,每次要計算檔案中的單詞時,都要執行所有這些程式碼將非常麻煩!為了使事情更有效率,讓我們透過在Python直譯器中定義它們,將上述程式碼轉換為三個可重用的函式,分別稱為load_text()clean_text()count_words()

def load_text(file_path):
    """載入文字檔案"""
    with open(file_path) as file:
        text = file.read()
    return text

def clean_text(text):
    """清理文字"""
    text = text.lower()
    from string import punctuation
    for p in punctuation:
        text = text.replace(p, "")
    return text

def count_words(file_path):
    """計算單詞數量"""
    text = load_text(file_path)
    text = clean_text(text)
    words = text.split()
    from collections import Counter
    word_counts = Counter(words)
    return word_counts

內容解密:

  1. load_text()函式用於載入文字檔案,並將其內容作為字串傳回。
  2. clean_text()函式用於清理文字,將其轉換為小寫並刪除標點符號。
  3. count_words()函式結合了上述兩個函式,並使用Counter物件計算單詞數量。

Docker容器管理

在開發和測試Python套件時,我們可以使用Docker容器來隔離環境。要啟動一個已停止的容器,可以使用以下命令:

$ docker start -a 653daa2cd48e

如果要完全刪除容器,可以使用docker rm命令:

$ docker rm 653daa2cd48e

這將刪除容器,包括其中安裝的任何包或虛擬環境。但是,新增到工作目錄的所有檔案和目錄將保留在您的機器上。

開發 Python 套件:結構與建立

在前面的章節中,我們已經撰寫了一些有用的 Python 函式,例如 load_text()clean_text()count_words()。然而,如果我們離開 Python 直譯器,這些函式就會遺失,我們將需要在新的會話中重新定義它們。Python 套件的概念正是為瞭解決這個問題:我們可以將 Python 程式碼儲存在一個套件中,以便我們和其他人可以在任何時間和任何專案中安裝、匯入和使用。

在本章的其餘部分,我們將致力於將我們撰寫的程式碼封裝成一個名為 pycounts 的 Python 套件。

套件結構簡介

要開發我們的 pycounts 套件,我們首先需要建立一個適當的目錄結構。Python 套件由特定的目錄結構組成,通常包括以下內容:

  • 一個以套件名稱命名的根目錄,例如 pycounts/;
  • 一個或多個 Python 模組(包含 Python 程式碼的副檔名為 .py 的檔案),位於 src/pycounts/ 子目錄中;
  • 一個名為 pyproject.toml 的檔案,包含有關如何建置和安裝套件的指示;
  • 重要的檔案,例如根目錄中的 README,以及 docs/ 子目錄中的其他檔案;
  • tests/ 子目錄中的測試。

以下是一個名為「pycounts」的套件範例結構,包含兩個模組(「moduleA」和「moduleB」)。

pycounts
├── CHANGELOG.md 
├── CONDUCT.md 
├── CONTRIBUTING.md 
├── docs 
│   └── ... 
├── LICENSE 
├── README.md 
├── pyproject.toml 
├── src 
│   └── pycounts 
│       ├── __init__.py 
│       ├── moduleA.py 
│       └── moduleB.py 
└── tests 
    └── ...

建立套件結構

大多數開發人員使用預先製作的範本來設定 Python 套件的目錄結構。我們將使用 cookiecutter 工具(在第 2.2.2 節中安裝)來建立我們的套件結構。

cookiecutter 是一個從預先製作的範本中填充目錄結構的工具。人們已經開發並開源了許多用於不同專案的 cookiecutter 範本,例如建立 Python 套件、R 套件、網站等。你可以透過搜尋線上託管服務(如 GitHub)來找到這些範本。我們已經開發了自己的 py-pkgs-cookiecutter Python 套件範本來支援這本文;它託管在 GitHub 上。

要使用此範本建立套件目錄結構,你可以導航到想要從命令列建立套件的目錄,然後執行以下命令。執行此命令後,你將被提示提供將用於建立套件檔案和目錄結構的資訊。下面我們提供了一個如何回應這些提示的範例,並在表 3.1 中解釋了它們的含義。

$ cookiecutter https://github.com/py-pkgs/py-pkgs-cookiecutter.git
author_name [Monty Python]: Tomas Beuzen
package_name [mypkg]: pycounts
package_short_description []: Calculate word counts in a text file!
package_version [0.1.0]:
python_version [3.9]:
Select open_source_license:
1 - MIT
2 - Apache License 2.0
3 - GNU General Public License v3.0
4 - Creative Commons Attribution 4.0
5 - BSD 3-Clause
6 - Proprietary
7 - None
Choose from 1, 2, 3, 4, 5, 6 [1]:
Select include_github_actions:
1 - no
2 - ci
3 - ci+cd
Choose from 1, 2, 3 [1]:

表 3.1:py-pkgs-cookiecutter 範本提示的描述

提示關鍵字描述
author_name、package_name、package_short_description這些是不言自明的。請注意,我們將把我們的 pycounts 套件釋出到 Python 的主要套件索引 PyPI,其中名稱必須是唯一的。如果你計劃遵循本教程,你應該為你的套件選擇一個唯一的名稱。像 pycounts_[你的首字母] 這樣的名稱可能是合適的,但你可以在 PyPI 上搜尋它以檢查該名稱是否已被佔用。我們在第 4.2.2 節中提供了有關選擇好套件名稱的指導。
package_version你的套件版本。大多數套件使用語義版本控制,其中版本號由三個整數 A.B.C 組成。A 是「主要」版本,B 是「次要」版本,C 是「修補程式」版本。套件的第一個版本通常從 0.1.0 開始,然後從那裡遞增。我們將在第 7 章:釋出和版本控制中討論版本控制。
python_version你的套件將支援的最低 Python 版本。我們將在第 3.6.1 節中更多地討論版本和約束。

內容解密:

此段落描述瞭如何使用 cookiecutter 建立 Python 套件的基本結構。主要步驟包括執行 cookiecutter 命令並根據提示輸入相關資訊,例如作者名稱、套件名稱、版本等。其中,author_namepackage_namepackage_short_description 是必填資訊,而 package_versionpython_version 則有預設值,可以根據需要進行修改。此外,還需要選擇開源許可證和是否包含 GitHub Actions。這些資訊將用於建立套件的目錄結構和相關檔案。

程式碼解析:

$ cookiecutter https://github.com/py-pkgs/py-pkgs-cookiecutter.git

這行指令是使用 cookiecutter 從指定的 GitHub 倉函式庫下載並執行範本,建立新的 Python 套件專案。

pycounts
├── CHANGELOG.md 
├── CONDUCT.md 
├── CONTRIBUTING.md 
├── docs 
│   └── ... 
├── LICENSE 
├── README.md 
├── pyproject.toml 
├── src 
│   └── pycounts 
│       ├── __init__.py 
│       ├── moduleA.py 
│       └── moduleB.py 
└── tests 
    └── ...

這個目錄結構展示了一個典型的 Python 套件專案所包含的主要檔案和子目錄。其中,src 目錄下的 pycounts 目錄包含了套件的核心程式碼,tests 目錄用於存放測試程式碼,而 docs 目錄則包含了專案的檔案。其他檔案如 README.mdLICENSEpyproject.toml 等提供了專案的基本資訊、許可證和建置組態等。

3.2 套件結構解析

在建立 Python 套件的過程中,py-pkgs-cookiecutter 範本提供了一個基本的目錄結構。這個結構包含多個重要檔案和目錄,用於支援套件的開發、測試和檔案編寫。

關鍵檔案與目錄

  1. src/pycounts/:存放套件的原始碼,包括 __init__.pypycounts.py

    • __init__.py:定義套件的初始化過程。
    • pycounts.py:包含套件的主要功能實作。
  2. tests/:包含測試檔案,用於驗證套件的功能正確性。

    • test_pycounts.py:對 pycounts.py 中的函式進行單元測試。
  3. docs/:存放套件的檔案檔案,包括使用說明、變更日誌等。

    • index.md:檔案的首頁。
    • example.ipynb:提供使用範例的 Jupyter Notebook。
  4. pyproject.toml:定義套件的建置設定和相依性。

  5. README.md:套件的簡介和使用。

  6. LICENSE:定義套件的授權條款。

設定版本控制

在開發套件之前,建議將專案置於版本控制之下,以追蹤變更和管理協作。

本地版本控制

  1. 初始化 Git 儲存函式庫:

    $ cd pycounts
    $ git init
    
  2. 將所有檔案納入版本控制並提交:

    $ git add .
    $ git commit -m "initial package setup"
    

遠端版本控制

  1. 在 GitHub 上建立新的儲存函式庫,並設定為遠端儲存函式庫。
  2. 連結本地和遠端儲存函式庫,並推播本地內容:
    $ git remote add origin [email protected]:TomasBeuzen/pycounts.git
    $ git branch -M main
    $ git push -u origin main
    

程式碼封裝

將開發的函式(如 load_text()clean_text()count_words())放入 src/pycounts/pycounts.py 模組中,並確保正確匯入所需的模組。

程式碼範例

from collections import Counter
from string import punctuation

def load_text(input_file):
    """從文字檔載入文字並以字串形式傳回。"""
    with open(input_file, "r") as file:
        text = file.read()
    return text

def clean_text(text):
    """將字串轉換為小寫並移除標點符號。"""
    text = text.lower()
    for p in punctuation:
        text = text.replace(p, "")
    return text

內容解密:

  1. load_text() 函式:此函式用於開啟並讀取指定的文字檔,將其內容以字串形式傳回。

    • 使用 with open() 陳述式確保檔案在讀取後正確關閉。
    • 引數 input_file 指定要讀取的檔案路徑。
  2. clean_text() 函式:此函式對輸入的字串進行清理,將其轉換為小寫並移除所有標點符號。

    • 使用 text.lower() 將字串轉換為小寫。
    • 使用迴圈遍歷 string.punctuation 中的所有標點符號,並將其從字串中移除。

如何封裝Python套件

在開發Python套件時,封裝是一個重要的步驟。本章節將介紹如何使用poetry工具來封裝Python套件。

3.5 測試驅動您的套件程式碼

3.5.1 建立虛擬環境

在安裝和測試我們的套件之前,強烈建議設定一個虛擬環境。如前文第2.2.1節所述,虛擬環境提供了一個安全且隔離的空間來開發和安裝套件。如果您不想使用虛擬環境,可以直接跳到第3.5.2節。

有多個選項可用於建立和管理虛擬環境(例如condavenv)。本文將使用conda(在第2.2.1節中安裝),因為它是一個簡單、常用且有效的虛擬環境管理工具。

要使用conda建立一個名為pycounts的新虛擬環境,並包含Python,請在終端機中執行以下命令:

$ conda create --name pycounts python=3.9 -y

我們指定python=3.9,因為這是我們在第3.2.2節中指定的最低Python版本。

要使用這個新環境開發和安裝軟體,我們需要「啟動」它:

$ conda activate pycounts

在大多數命令列中,conda將新增一個字首,如(pycounts),以指示您正在使用的環境。任何時候您想要處理您的套件,都應該啟動其虛擬環境。您可以使用命令conda list檢視目前安裝在conda環境中的套件,並使用conda deactivate離開conda虛擬環境。

3.5.2 安裝您的套件

我們已經設定了套件結構,並填入了Python程式碼。如何安裝和使用我們的套件?有多個工具可用於開發可安裝的Python套件。最常見的是poetryflitsetuptools,我們將在第4.3.3節中比較它們。在本文中,我們將使用poetry(在第2.2.2節中安裝);它是一個現代化的封裝工具,提供簡單且高效的命令來開發、安裝和發布Python套件。

在由poetry管理的套件中,pyproject.toml檔案儲存了套件的所有元資料和安裝指令。由py-pkgs-cookiecutter為我們的pycounts套件建立的pyproject.toml檔案如下所示:

[tool.poetry]
name = "pycounts"
version = "0.1.0"
description = "計算文字檔中的字數。"
authors = ["Tomas Beuzen"]
license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.9"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

表格3.2提供了該檔案中每個標題的簡要描述(在TOML檔案術語中稱為「表格」)。

表格3.2:pyproject.toml中的表格描述
TOML表格描述
[tool.poetry]定義套件元資料。需要名稱、版本、描述和作者。
[tool.poetry.dependencies]識別套件的相依性——即套件所依賴的軟體。我們的pycounts套件只依賴Python 3.9或更高版本,但我們稍後將新增其他相依性。
[tool.poetry.dev-dependencies]識別開發相依性——即開發所需的相依性,例如執行測試或建立檔案。我們稍後將新增開發相依性到我們的pycounts套件。
[build-system]識別建立套件所需的建置工具。我們將在第3.10節中進一步討論。

使用由py-pkgs-cookiecutter範本為我們設定的pyproject.toml檔案,我們可以使用命令列中的poetry install命令,從根套件目錄安裝我們的套件:

$ poetry install
Updating dependencies
Resolving dependencies... (0.1s)
Writing lock file
Installing the current project: pycounts (0.1.0)

當您執行 poetry install 時, poetry 建立一個 poetry.lock 檔案,其中包含您在開發套件時安裝的所有相依性的記錄。對於其他任何人在您的專案上工作(包括您未來的自己),執行 poetry install 將從 poetry.lock 安裝相依性,以確保他們具有與您開發套件時相同的相依性版本。

安裝完我們的套件後,我們現在可以在Python會話中匯入並使用它。在那之前,我們需要一個文字檔來測試我們的套件。您可以自由使用任何文字檔,但我們將建立與本章前面使用的相同的「Python之禪」文字檔,方法是執行以下命令:

$ python -c "import this" > zen.txt

現在,我們可以開啟一個Python直譯器,並使用以下程式碼從我們的 pycounts 模組匯入並使用 count_words() 函式:

>>> from pycounts.pycounts import count_words
>>> count_words("zen.txt")
Counter({'is': 10, 'better': 8, 'than': 8, 'the': 6, 'to': 5, 'of': 3, 'although': 3, 'never': 3, ...})

相關程式碼解析

def count_words(input_file):
    """計算字串中的唯一字詞數量。"""
    text = load_text(input_file)
    text = clean_text(text)
    words = text.split()
    return Counter(words)

內容解密:

  1. load_text(input_file):讀取指設定檔案中的文字內容。
  2. clean_text(text):清理文字內容,可能包括移除標點符號、轉換大小寫等。
  3. text.split():將清理後的文字分割成單詞列表。
  4. Counter(words):計算單詞列表中每個單詞的出現次數,傳回一個字典,其中鍵是單詞,值是出現次數。

這個函式透過讀取檔案、清理文字、分割單詞和計數單詞,實作了計算文字檔中單詞數量的功能。