週末學習 Python:從零到完成一個專案
我們如何快速學習一門新的程式語言呢?在這篇文章中,我們將分享我們透過建構一個完整專案來學習 Python 的週末經驗。
介紹
作為身份服務的 Logto,提供各種程式語言和框架的無縫體驗是至關重要的。這通常涉及到創建軟體開發工具包(SDK)。然而,當程式語言不在我們的技術堆棧內且我們的團隊不熟悉時,構建一個強大的 SDK 就變成了一個挑戰。
Python 對我們來說就是這樣一個挑戰。儘管我們有很多 Python 使用者,但我們缺乏一個 Python SDK,這一直是一個揪心的問題。決定要解決這個問題,我開始彌補這個差距。
儘管我有多年的編程經驗,但 Python 對我來說仍然是相對未知的領域。儘管幾年前我曾經簡單地使用過 Python 2 來寫一些簡單的腳本,但我的知識已經過時了。然而,是時候深入了解了!
第一天:打下基礎
定義目標
根據我的經驗,最有效的學習新程式語言的方法是構建一個完整的專案。幸運的是,我們的目標很明確:為網路應用程式構建一個 Logto 的 Python SDK。
不要急於進入編程,讓我們先分解它。這是列表:
- 建立 Logto 客戶端以執行登入、登出、使用者信息和令牌管理等任務。
- 提供一個教程和一個示例專案展示 SDK 的用法。
- 將 SDK 發布到某個地方,讓使用者可以輕鬆安裝。
看來任務 1 的工作最多,因此我們需要確認範圍並繼續分解。這一步對於確保專案的邊界,避免範圍擴張和過度設計是至關重要的。
我們團隊以前的工作為我節省了大 量時間:
- SDK 規約概述了 SDK 的結構和 API 設計。
- 不同語言的現有 SDK 提供了對於 Python 的模式和潛在增強方面的見解。
參照這些資源,我可以對要做的事情有一個清晰的了解。雖然具體細節超出本文的範圍,但讓我們繼續前進。
設置環境
我用的是 Mac,所以 Python 已經安裝好了。然而,我在想是否有更好的方式來管理 Python 的版本(我聽說過版本兼容性的痛苦),就像 Node.js 的 nvm
一樣。很快,我找到了 pyenv 並直接開始安裝它。
接下來的議程:包和依賴管理器。通常它們是耦合的。那為什麼不選擇 pip
(Python 的默認)呢?如果你查看 requirements.txt
,你會發現它只是一個包含版本的包列表。這對於可能被其他專案使用的 SDK 來說是不夠的。例如,我們可能需要增加一些開發所需的包,但我們不想把它們包含在最終的 SDK 中。requirements.txt
太簡單而無法處理這個問題。
當你的技術堆疊中有其他程式語言時,其中的一個好處就是你可以搜索到“Python 的等效功能”。所以我搜尋了"package.json Python equivalent",找到了一個絕佳的候選者 Poetry:
- 它作為包管理器、依賴管理器和虛擬環境管理器運作。
- 它有一個
pyproject.toml
文件,類似於 Node.js 的package.json
。 - 它使用鎖定文件記錄精確的依賴版本。
現代命令行接口通常包含專為新專案量身定制的 init
命令。Poetry 也是如此。我運行了命令,它為我創建了一個 pyproject.toml
文件。
第一行代碼
最後,編寫代碼的時刻到了。以經典的 "Hello, World!" 程式開始總是個好主意。在學習程式語言時,全功能的 IDE 並不是總是必要的;一個擁有強大社群支持的編輯器,比如 VS Code,就已經足夠。
考慮到我們的 SDK 專注於網絡應用,我開始使用流行框架 Flask 編寫一個簡單的網路伺服器。
利用 Poetry 的能力,只需運行 poetry add flask
安裝 Flask。然後,按照 Flask 的官方快速入門指南,我編寫了一個 'hello.py' 文件,包含以下代碼片段:
通過運行 flask --app hello run
啟動伺服器,在瀏覽器中導航到 http://localhost:5000 得到了預期的結果。成功了!
作為一個初學者,我不急於編寫更多的代碼。相反,從代碼片段中可以接收到很多信息:
- 使用
from x import y
導入模塊或類。 - 行末不需要分號(哦,不)。
- 通過輸入任意名稱並賦值給它來定義一個新變數。
- 創建類的實例時不需要
new
關鍵字。 - Python 支持裝飾器,
@app.route
作為裝飾器將方法註冊為路徑處理程序。- 該方法的返回值被解釋為響應體。
- 可以使用
def
關鍵字定義一個函數。
如你所見,如果我們嘗試去理解代碼的每一行而不僅僅是“讓它工作”,我們可以從中學到很多。同時,Flask 的官方文檔進一步詳細解釋了這個代碼片段。
專案啟動
現在是時候開始專案了。我很快定義了一個 LogtoClient
類,并嘗試添加一些屬性和方法來熟悉語言:
隨後,將類與 Flask 結合:
開始感覺像是一個真正的專案了。但我覺得少了點什麼:一個類型系統。
類型系統
因為這是個 SDK,加入一個類型系統可以幫助使用者理解 API 並在開發時減少錯誤的概率。
Python 在 3.5 版本引入了類型提示。它不像 TypeScript 那麼強大,但總比沒有好。我為 LogtoClient
類增加了一些類型提示:
看起來好多了。當涉及到像具有預定義鍵的物件這樣的複雜類型時,挑戰才出現。例如,我們需要定義一個 LogtoConfig
類來表示配置物件:
它看起來還可以,但很快我們就需要面對從 JSON 編碼、解碼和驗證物件的問題。
經過一些研究,我選擇了 pydantic 作為解決方案。它是一個支持類型提示的資料驗證庫。它支持多種 JSON 功能,而不需要繁瑣的樣板代碼。
因此,LogtoConfig
類可以重寫為:
這也讓我通過在類名後附加括號學到了 Python 中的類繼承。
非同步操作
在 Logto SDK 中,我們需要向 Logto 伺服器發送 HTTP 請求。如果你有 JavaScript 的經驗,“回調地獄”這個短語可能會讓你想起一個問題。這是一個處理非同步操作時的常見問題。現代程式語言提供了類似 Promise
或coroutine
的解決方案。
幸運的是,Python 有一個內建的 async
和 await
解決方案。在使用它們之前,確保與流行框架的兼容性。在 Flask 中,可以通過安裝 async
相關組件並使用 async def
代替 def
來實現:
然後,我們可以使用 await
來等待非同步操作的結果。
HTTP 請求
HTTP 請求是個有趣的話題。幾乎每種程式語言都有它的原生解決方案,但開發者通常會選擇第三方庫以便使用。舉幾個例子:
- JavaScript:
XMLHttpRequest
vs.fetch
vs.axios
- Swift:
URLSession
vs.Alamofire
- Java:
HttpURLConnection
vs.OkHttp
Python 也是如此。我最終決定使用 aiohttp,因為它支持 async
和 await
,並且流行度也很高。
Copilot 的神奇
在 Copilot 出現之前,我們應該現在來到了編寫業務邏輯的繁瑣部分。在有 SDK 規約和其他 SDK 的幫助下,我可以為每個方法編寫描述性註釋,再編寫代碼。
這增加了代碼的可讀性,並幫助開發者通過代碼智能直接在 IDE 或編輯器中了解 API。
例如,考慮 generateCodeChallenge
方法,註釋可以這樣寫:
這對大型語言模型(LLM)構成了一個很好的提示:由清晰的註釋包含的方法定義。Copilot 沒有讓人失望:
可能需要做一些調整,但這並不重要。它已經改變了遊戲規則。
總結
這大致就是第一天所取得的進展。這是漫長的一天,但有了現代工具和技術,比我預期的要好得多。
第二天:提高標準
基於第一天的工作,業務邏輯快速完成。然而對於一個 SDK 來說,這仍然不夠。以下是第二天的任務:
- 添加單元測試。
- 強制代碼格式化。
- 驗證 Python 版本相容性。
- 添加持續整合。
- 發布 SDK。
單元測試
單元測試多次救了我們,所以我不會跳過。以下是在編寫單元測試時的常見考慮:
- 如何組織和執行測試?
- 如何驗證結果?
- 如何運行非同步測試?(這似乎是理所當然的,但在某些語言中它偶爾會引發問題。)
- 如何模擬依賴?(不要深入這個話題,除非它是必要的,因為它可能會帶來麻煩。)
- 如何生成代碼覆蓋報告?
考慮到這些問題,我發現內建的 unittest
模組在某些情況下不太夠用。所以我選擇了 pytest 作為測試框架。它支持非同步測試並且看起來足夠成熟。
這段旅程向我揭示了一些有趣的新概念,比如 fixture
。這對於在其他語言中編寫代碼時的思維方式也有幫助。
代碼格式化
每種語言都有自己的代碼格式化風格。就我個人而言,統一的格式讓我開心和舒適;這對代碼審查和合作也有益。
我選擇了一個主觀的格式化工具並堅持使用它,而不是商討“最佳”風格的論點。
Black 看起來是個不錯的選擇。唯一的煩惱是無法修改的制表符大小。但這不是大事,我選擇適應它。
Python 版本相容性
作為一個 SDK,應該能夠兼容流行的 Python 版本。通過搜尋“python version usage statistics”,我決定使用 Python 3.8 作為最低版本。
環境管理器的優勢現在顯現出來。我可以輕鬆地通過運行 pyenv local 3.8
和 poetry env use 3.8
切換 Python 版本。然後可以運行測試來揭示兼容性問題。
持續整合
持續整合保證了每次代碼變更的質量。由於我們的儲存庫托管在 GitHub 上,所以 GitHub Actions 自然是首選。
核心工作流遵循簡單的原則:
- 設置環境。
- 安裝依賴。
- 構建專案(Python 不需要)。
- 執行測試。
GitHub Actions 有一個好的社群,所以只需幾分鐘就能構建工作流。
通過使用矩陣策略,我們可以在不同的 Python 版本上運行工作流,甚至在不同的作業系統上運行。
發布 SDK
最後一步就是發佈 SDK。對於公開的包,這通常可以通過提交到官方語言專用的包註冊中心來完成。例如 Node.js 的 npm,Python 的 PyPI,和 Swift 的 CocoaPods。
Poetry 是我的指導明星。只需運行 poetry publish
將包發布到 PyPI。就是這麼簡單。
結語
這是一場引人入勝的旅程。沒有開源社群的幫助,這會更加困難。向所有的貢獻者致敬!
這裡有一些一般性的收穫:
- 精確定義目標並將其分解,然後始終記住目標。
- 設置一個穩定且可重現的開發環境。
- 盡可能多地使用(好的)工具。
- 優先考慮內建或現有的解決方案。
- 理解語言慣例和你寫的每一行代碼。
- 然而,不要拘泥於細節。
- 對於清晰、描述性的任務,使用 Copilot。
你可以在這個儲存庫中找到最終結果。通過相同的策略,我還快速構建了 Logto PHP SDK。如果你有任何建議,請隨時告訴我們。
希望這篇文章對於學習一門新的程式語言有幫助。快樂編程!