繁體中文(台灣)
  • python
  • programming
  • learning
  • sdk

週末學習 Python:從零到完成一個專案

我們如何快速學習一門新的程式語言呢?在這篇文章中,我們將分享我們透過建構一個完整專案來學習 Python 的週末經驗。

Gao
Gao
Founder

介紹

作為身份服務的 Logto,提供各種程式語言和框架的無縫體驗是至關重要的。這通常涉及到創建軟體開發工具包(SDK)。然而,當程式語言不在我們的技術堆棧內且我們的團隊不熟悉時,構建一個強大的 SDK 就變成了一個挑戰。

Python 對我們來說就是這樣一個挑戰。儘管我們有很多 Python 使用者,但我們缺乏一個 Python SDK,這一直是一個揪心的問題。決定要解決這個問題,我開始彌補這個差距。

儘管我有多年的編程經驗,但 Python 對我來說仍然是相對未知的領域。儘管幾年前我曾經簡單地使用過 Python 2 來寫一些簡單的腳本,但我的知識已經過時了。然而,是時候深入了解了!

第一天:打下基礎

定義目標

根據我的經驗,最有效的學習新程式語言的方法是構建一個完整的專案。幸運的是,我們的目標很明確:為網路應用程式構建一個 Logto 的 Python SDK。

不要急於進入編程,讓我們先分解它。這是列表:

  1. 建立 Logto 客戶端以執行登入、登出、使用者信息和令牌管理等任務。
  2. 提供一個教程和一個示例專案展示 SDK 的用法。
  3. 將 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 的經驗,“回調地獄”這個短語可能會讓你想起一個問題。這是一個處理非同步操作時的常見問題。現代程式語言提供了類似 Promisecoroutine 的解決方案。

幸運的是,Python 有一個內建的 asyncawait 解決方案。在使用它們之前,確保與流行框架的兼容性。在 Flask 中,可以通過安裝 async 相關組件並使用 async def 代替 def 來實現:

然後,我們可以使用 await 來等待非同步操作的結果。

HTTP 請求

HTTP 請求是個有趣的話題。幾乎每種程式語言都有它的原生解決方案,但開發者通常會選擇第三方庫以便使用。舉幾個例子:

  • JavaScript: XMLHttpRequest vs. fetch vs. axios
  • Swift: URLSession vs. Alamofire
  • Java: HttpURLConnection vs. OkHttp

Python 也是如此。我最終決定使用 aiohttp,因為它支持 asyncawait,並且流行度也很高。

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.8poetry 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。如果你有任何建議,請隨時告訴我們。

希望這篇文章對於學習一門新的程式語言有幫助。快樂編程!