Python 專案的封裝和佈署流程中,確保程式碼的可重複性、可維護性以及跨平台一致性至關重要。本文介紹的最佳實踐涵蓋了從 setup.py 的撰寫、依賴管理到 Docker 映像構建的完整流程。利用 setuptools 函式庫簡化封裝流程,並使用 pip-tools 鎖定依賴版本,確保開發和生產環境的一致性。此外,Docker 容器技術的應用更進一步提升了應用程式的可攜性,使其在不同環境中都能穩定執行。同時,文章也強調了軟體架構中抽象層設計的重要性,透過介面卡模式和依賴注入等設計原則,有效降低程式碼耦合度,提升程式碼的可維護性和可擴充套件性。
封裝Python專案的最佳實踐
在開發Python專案時,正確地封裝和分發是非常重要的。這不僅確保了專案的可重複性和可維護性,還使得其他開發者能夠輕鬆地安裝和使用你的專案。
使用setup.py進行專案封裝
setup.py是Python專案封裝的核心檔案,它包含了專案的後設資料和依賴關係。透過使用setuptools函式庫,我們可以輕鬆地建立一個setup.py檔案。
from setuptools import find_packages, setup
with open("README.rst", "r") as longdesc:
long_description = longdesc.read()
install_requires = ["sanic>=20,<21"]
setup(
name="web",
description="Library with helpers for the web-related functions",
long_description=long_description,
author="Dev team",
version="0.1.0",
packages=find_packages(where="src/"),
package_dir={"": "src"},
install_requires=install_requires,
)
內容解密:
find_packages(where='src/'):自動發現src/目錄下的所有套件,避免將測試檔案或無關檔案納入專案中。package_dir={'': 'src'}:指定套件所在的目錄,這裡是src/。install_requires=install_requires:定義專案所需的依賴套件及其版本範圍。long_description=long_description:從README.rst檔案中讀取長描述,提供給PyPI使用。
管理依賴關係
為了確保專案的可重複性,我們需要管理好依賴關係。這包括了指定依賴套件的版本範圍和使用工具來編譯requirements.txt檔案。
使用pip-tools編譯requirements.txt
pip install pip-tools
pip-compile > requirements.txt
內容解密:
pip install pip-tools:安裝pip-tools,這是一個用於管理依賴關係的工具。pip-compile > requirements.txt:根據setup.py中的依賴關係編譯出requirements.txt檔案,列出所有依賴套件的確切版本。
Docker化你的應用
為了使應用程式在不同環境中平滑執行,使用Docker是一個好方法。Docker可以確保你的應用程式在任何地方都以相同的方式執行。
建立Docker映像
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
內容解密:
FROM python:3.9-slim:使用官方的Python 3.9映像作為基礎映像。COPY requirements.txt .:將requirements.txt檔案複製到容器中。RUN pip install --no-cache-dir -r requirements.txt:安裝依賴套件。COPY . .:將當前目錄下的所有檔案複製到容器中。CMD ["python", "app.py"]:指定容器啟動時執行的命令。
管理依賴與版本控制的最佳實踐
在軟體開發過程中,正確管理依賴是確保專案穩定性和可維護性的關鍵。本文將探討如何使用工具和技術來有效管理依賴,並確保專案的版本控制。
使用 pip-compile 管理依賴
為了確保專案的依賴被正確管理,我們可以使用 pip-compile 工具從 setup.py 檔案生成 requirements.txt 檔案。這樣可以確保每次構建 Docker 映像時,都使用相同的依賴版本,從而實作版本控制的確定性。
pip-compile setup.py
這個命令會生成一個 requirements.txt 檔案,包含了所有依賴的精確版本。我們應該在 Dockerfile 中使用這個檔案來安裝依賴,以確保每次構建都是一致的。
內容解密:
pip-compile工具用於根據setup.py中的依賴定義生成requirements.txt。- 使用
requirements.txt安裝依賴可確保 Docker 構建的一致性。 - 將
requirements.txt檔案置於版本控制之下,以便追蹤依賴的變更。
管理依賴的其他考量
預設情況下,pip 會從公共 PyPI 倉函式庫安裝依賴。然而,這種做法有一些限制和問題,例如依賴於外部服務的可用性,以及無法發布內部套件。
內容解密:
- 使用內部依賴伺服器可以解決對外部服務的依賴問題。
- 內部套件應釋出到公司內部的 PyPI 伺服器,以保護智慧財產權。
- 需要定期更新內部倉函式庫,以確保獲得最新的安全修補和功能。
自動化依賴更新
為了保持依賴的最新狀態,我們可以組態工具自動傳送 Pull Request 以更新依賴版本。同時,也應該組態自動安全檢查,以確保依賴的安全性。
內容解密:
- 組態工具自動更新
requirements.txt,以便在有新版本時通知開發者。 - 自動化安全檢查有助於及時發現和修復安全漏洞。
- 持續整合(CI)流程應包括自動化測試,以確保更新依賴後專案仍然穩定。
版本管理與穩定性
在追求最新版本的同時,也需要考慮穩定性。軟體版本管理需要平衡穩定性和最新功能之間的取捨。
內容解密:
- 軟體版本應遵循一定的規則,如 PEP-440,以確保版本號具有明確的含義。
- 更新依賴版本時,應考慮是否需要發布新版本的 artifact。
- 正確的版本管理有助於維護專案的穩定性和可預測性。
容器化技術在Python專案中的應用
在軟體開發領域,容器化技術已經成為一種主流的佈署方式。相較於傳統的虛擬機器,容器提供了更輕量級、更快速的佈署方案。本章節將重點介紹Docker容器技術在Python專案中的應用。
為什麼需要容器化?
在過去,Python專案的佈署常常因為環境差異而導致各種問題。由於Python是一種解釋型語言,程式碼需要在目標環境中由Python直譯器執行。這意味著開發者需要確保生產環境中的Python版本與開發環境一致。此外,依賴套件的管理也是一大挑戰,尤其是當某些套件依賴於特定的C函式庫或作業系統層級的元件時。
容器化技術,如Docker,提供瞭解決這些問題的方案。透過將Python應用程式封裝成Docker映像,我們可以確保應用程式在不同環境中的一致性,從而簡化開發、測試和佈署流程。
Docker容器基礎
Docker容器需要一個映像來執行,而這個映像是根據其他基礎映像建立的。我們建立的映像也可以作為其他容器的基礎映像。在某些情況下,如果我們的應用程式有多個容器分享相同的基礎組態,我們會希望建立一個基礎映像來安裝特定的套件及其依賴項。
使用Docker的好處
- 可攜性:容器化技術使得應用程式能夠在不同環境中平滑執行,無需擔心環境差異帶來的問題。
- 易於開發和測試:透過容器化,我們可以輕鬆地在本地環境中重現生產環境,從而簡化測試流程。
- 維護和擴充套件性:容器化技術符合「關注點分離」(Separation of Concerns, SoC)的設計原則。每個容器封裝了一組獨立的功能,使得維護和擴充套件變得更加容易。
例項探討:食品配送服務的狀態追蹤API
假設我們正在開發一個食品配送服務,其中包含一個用於追蹤訂單狀態的REST API服務。這個服務需要從資料函式庫中取得訂單資訊,並以JSON格式傳回給客戶端。
服務設計
我們的服務主要關注兩個方面:
- 資料存取:從資料函式庫中取得訂單資訊。
- 資料呈現:將訂單資訊以JSON格式傳回給客戶端。
為了保持服務的可維護性和可擴充套件性,我們將這兩個關注點抽象成獨立的Python套件。主應用程式負責核心邏輯,並使用這兩個套件來完成資料存取和呈現的工作,如圖10.1所示。
建立Docker容器
在下一節中,我們將介紹如何為Python專案建立Docker容器。具體步驟包括建立Dockerfile、構建Docker映像以及執行Docker容器等。
Dockerfile範例
# 使用官方Python映像作為基礎
FROM python:3.9-slim
# 設定工作目錄
WORKDIR /app
# 複製需求檔案
COPY requirements.txt .
# 安裝依賴套件
RUN pip install --no-cache-dir -r requirements.txt
# 複製應用程式碼
COPY . .
# 暴露服務埠
EXPOSE 8000
# 執行命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
內容解密:
FROM python:3.9-slim:使用官方的Python 3.9映像作為基礎映像,slim版本是為了減小映像大小。WORKDIR /app:設定容器內的工作目錄為/app。COPY requirements.txt .:將本地的requirements.txt檔案複製到容器內的工作目錄。RUN pip install --no-cache-dir -r requirements.txt:安裝requirements.txt中指定的依賴套件。COPY . .:將當前目錄下的所有檔案複製到容器內的工作目錄。EXPOSE 8000:暴露容器的8000埠,以便外部存取。CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]:設定容器的預設執行命令,使用uvicorn啟動FastAPI應用程式。
軟體架構中的抽象層設計
在現代軟體開發中,良好的架構設計是確保系統可維護性、可擴充套件性和可讀性的關鍵。本文將探討如何透過抽象層設計來實作這些目標,並以一個具體的範例來說明其實作方式。
抽象層的重要性
在軟體開發中,直接與底層技術(如資料函式庫或網頁框架)互動可能會導致程式碼與這些技術緊密耦合。這種緊耦合會使程式碼難以維護和適應變化。透過引入抽象層,可以有效地隔離核心業務邏輯與外部技術細節,從而提高程式碼的靈活性和可維護性。
範例:配送服務應用程式
考慮一個名為「Web 服務」的應用程式,它使用了兩個 Python 套件,其中一個用於連線資料函式庫。這種設計展示瞭如何透過抽象化元件來實作有效的隔離。
程式碼結構
首先,我們定義了一些業務規則的類別(Domain Models),這些類別代表了不同的訂單狀態,例如 DispatchedOrder、OrderInTransit 和 OrderDelivered。這些類別是純粹的業務物件,不依賴於任何特定的技術實作。
from typing import Union
class DispatchedOrder:
"""An order that was just created and notified to start its delivery."""
status = "dispatched"
def __init__(self, when):
self._when = when
def message(self) -> dict:
return {
"status": self.status,
"msg": "Order was dispatched on {0}".format(self._when.isoformat()),
}
class OrderInTransit:
"""An order that is currently being sent to the customer."""
status = "in transit"
def __init__(self, current_location):
self._current_location = current_location
def message(self) -> dict:
return {
"status": self.status,
"msg": "The order is in progress (current location: {})".format(self._current_location),
}
class OrderDelivered:
"""An order that was already delivered to the customer."""
status = "delivered"
def __init__(self, delivered_at):
self._delivered_at = delivered_at
def message(self) -> dict:
return {
"status": self.status,
"msg": "Order delivered on {0}".format(self._delivered_at.isoformat()),
}
class DeliveryOrder:
def __init__(self, delivery_id: str, status: Union[DispatchedOrder, OrderInTransit, OrderDelivered]):
self._delivery_id = delivery_id
self._status = status
def message(self) -> dict:
return {"id": self._delivery_id, **self._status.message()}
應用程式中的使用
接下來,我們展示瞭如何在應用程式中使用這些物件。應用程式依賴於 storage 和 web 套件,但這些套件的內部實作細節對應用程式是透明的。
from storage import DBClient, DeliveryStatusQuery, OrderNotFoundError
from web import NotFound, View, app, register_route
class DeliveryView(View):
async def _get(self, request, delivery_id: int):
dsq = DeliveryStatusQuery(int(delivery_id), await DBClient())
try:
result = await dsq.get()
except OrderNotFoundError as e:
raise NotFound(str(e)) from e
return result.message()
register_route(DeliveryView, "/status/<delivery_id:int>")
抽象層的優勢
透過上述設計,我們實作了以下優勢:
- 宣告式程式設計:我們的程式碼宣告了我們想要解決的問題,而不是如何解決它。這使得程式碼更加直觀和易於理解。
- 隔離變化:核心業務邏輯與外部技術細節(如資料函式庫和網頁框架)隔離,使得系統更容易適應變化。
- 提高可維護性:由於程式碼與特定技術實作解耦,因此維護和修改變得更加容易。
容器化 Python 應用程式:技術深度解析
在現代軟體開發中,容器化技術已成為佈署和管理應用程式的主流選擇。本文將探討如何將 Python 應用程式容器化,特別是使用 Docker 技術的實務經驗與技術細節。
應用程式架構與介面卡模式
在開始容器化之前,瞭解應用程式的架構至關重要。該應用程式依賴於外部 API,例如 DeliveryStatusQuery,其實作細節被抽象化。這種設計使得更換底層實作變得相對容易,只需確保新的物件實作 get() 方法即可傳回 DeliveryOrder。
介面卡模式的應用
透過觀察應用程式的結構,可以推斷出 web 和 storage 等套件內部使用了介面卡設計模式。這種模式允許外部實作被適配到應用程式定義的 API,從而實作了依賴倒置原則。值得注意的是,View 類別繼承自 web 套件中的 View 類別,這是一種透過繼承實作的介面卡模式。
# DeliveryStatusQuery 的簡化範例
class DeliveryStatusQuery:
def __init__(self, delivery_id, db_client):
self.delivery_id = delivery_id
self.db_client = db_client
async def get(self):
# 實作細節被抽象化
pass
內容解密:
DeliveryStatusQuery類別封裝了查詢運單狀態的邏輯。- 建構子接受
delivery_id和db_client,展現了依賴注入的設計原則。 get()方法被設計為非同步,適合用於非同步 I/O 操作。
容器化 Python 應用程式
容器化應用程式涉及建立一個 Docker 容器,該容器包含執行應用程式所需的環境和依賴項。
目錄結構與依賴管理
典型的 Python 專案目錄結構如下:
├── Dockerfile
├── libs
│ ├── storage
│ └── web
├── Makefile
├── setup.py
└── statusweb
├── __init__.py
└── service.py
在這個結構中,setup.py 檔案負責定義應用程式的依賴項。
# setup.py 範例
from setuptools import find_packages, setup
with open("README.rst", "r") as longdesc:
long_description = longdesc.read()
install_requires = ["web==0.1.0", "storage==0.1.0"]
setup(
name="delistatus",
description="Check the status of a delivery order",
long_description=long_description,
author="Dev team",
version="0.1.0",
packages=find_packages(),
install_requires=install_requires,
entry_points={
"console_scripts": [
"status-service = statusweb.service:main",
],
},
)
內容解密:
setup.py使用setuptools來定義專案的中繼資料和依賴項。install_requires列出了專案所需的依賴項,包括自定義的web和storage套件。entry_points定義了一個可執行的指令status-service,它對映到statusweb.service模組中的main函式。