繁體中文(香港)
  • 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 提供了模式和可能增強的見解。

參考這些資源,我能清晰地知道該做什麼。雖然具體細節超出了本文範圍,但讓我們繼續。

設置環境

我使用的是 Mac,因此 Python 已經安裝好了。然而,我想知道是否有更好的方法來管理 Python 版本(我聽說過版本兼容性的痛苦),就像 Node.js 的 nvm 一樣。很快,我找到 pyenv 並直接開始安裝它。

接下來的議程:包和依賴項管理器。通常情況下它們是耦合的。那麼為什麼不選擇 pip(Python 默認)呢?如果你查看 requirements.txt,你會發現它只是一個帶有版本的包清單。這對於一個可能被其他項目使用的 SDK 來說是不夠的。例如,我們可能需要為開發添加某些包,但我們不希望將它們包含在最終的 SDK 中。requirements.txt 太簡單無法處理這個問題。

擁有其他編程語言的技術堆棧的好處之一是你可以搜索“Python 的等價物”。於是我搜索了“package.json Python 等價物”並找到了 Poetry,一個出色的候選者:

  • 它既是包管理器,也是依賴管理器和虛擬環境管理器。
  • 它有一個 pyproject.toml 文件,類似於 Node.js 中的 package.json
  • 它使用鎖定文件來記錄精確的依賴版本。

現代 CLI 通常包含一個 init 命令,專為新項目設計。Poetry 也不例外。我運行了這個命令,為我創建了一個 pyproject.toml 文件。

第一行代碼

終於,寫代碼的時候到了。以經典的“Hello, World!”程序作為開始總是一個不錯的選擇。在學習一門編程語言時,功能齊全的 IDE 並不是必需的;由一個強大的社區支持的編輯器,例如 VS Code,完全足夠。

鑒於我們的 SDK 主要針對網絡應用程序,我從一個簡單的 Web 服務器開始,使用流行的框架 Flask。

利用 Poetry 的功能,安裝 Flask 可以通過運行 poetry add 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 類可以重寫為:

這也教會了我通過在類名稱後附加括號來進行類的繼承。

異步操作

在 Logto SDK 中,我們需要向 Logto 服務器發起 HTTP 請求。如果你有 JavaScript 的經驗,"回調地獄" 這個短語可能讓你印象深刻。這是一個常見問題,當涉及到異步操作時。現代編程語言提供類似的解決方案,例如 Promisecoroutine

幸運的是,Python 有一個內置方案 asyncawait。在使用它們之前,確保與受歡迎框架的兼容性。在 Flask 中,可以通過安裝 async 擴展並使用 async def 來取代 def

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

HTTP 請求

HTTP 請求是一個有趣的話題。幾乎每種編程語言都有一個本地解決方案,但開發者通常使用第三方庫以便使用更方便。幾個例子:

  • JavaScript:XMLHttpRequestfetchaxios
  • Swift:URLSessionAlamofire
  • Java:HttpURLConnectionOkHttp

這對於 Python 來說也是如此。我的選擇是使用 aiohttp,因為它支持 asyncawait,並且受歡迎。

Copilot 的魔力

在 Copilot 之前,現在應該來到編寫業務邏輯的繁瑣部分。有了 SDK 的約定和其他 SDK 的幫助,在編寫代碼前我可以為每種方法編寫描述性的注釋。

它增加了代碼的可讀性,還有助於開發者通過代碼智能在 IDE 或編輯器中理解 API。

舉例來說,考慮 generateCodeChallenge 方法,注釋可以寫成:

這為大型語言模型(LLM)提出了一個很好的提示:通過清晰的注釋構成方法定義。Copilot 並沒有令我失望:

可能需要進行一些調整,但這無關緊要。它已經改變了遊戲規則。

收尾

這就是第一天取得的進展。這是漫長的一天,但有了現代工具和技術,比我預期的要好得多。

第二天:提升標準

以第一天的工作為基礎,業務邏輯被迅速完成。但對於一個 SDK 來說,這仍然不足。以下是第二天的任務:

  • 添加單元測試。
  • 強制執行代碼格式化。
  • 驗證 Python 版本兼容性。
  • 添加持續集成。
  • 發佈 SDK。

單元測試

單元測試曾多次拯救過我們,所以我不會跳過它。以下是編寫單元測試的常見考慮事項:

  • 如何組織和運行測試?
  • 如何斷言結果?
  • 如何運行異步測試?(這聽起來很簡單,但偶爾會在一些語言中引發問題。)
  • 如何模擬依賴項?(不要深入這個話題,除非它是必不可少的,因為它可能會帶你進入兔子洞。)
  • 如何生成代碼覆蓋率報告?

懷著這些問題,我發現內置的 unittest 模塊在某些情況下不太適用。於是我選擇了 pytest 作為測試框架。它支持異步測試,看起來足夠成熟。

這段旅程向我揭示了一些有趣的新概念,比如 fixture。這也可以在書寫其他語言的代碼時對觀念有所助益。

代碼格式化

每種語言都有其自己的代碼格式化風格。對我而言,統一的格式可以增強幸福感和舒適感;對代碼審查和協作也很有幫助。

與其瀏覽“最佳”風格的討論,我決定選擇一個有主見的格式化工具並堅持下去。

Black 看來是一個不錯的選擇。唯一的挫折是不可以修改的制表符大小。但這不是大問題,我選擇迎合它。

Python 版本兼容性

作為一個 SDK,應該與流行的 Python 版本兼容。通過搜索“python 版本使用統計”,我決定以 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。如有任何建議,請隨時告訴我們。

希望這篇文章對學習新編程語言有所幫助。祝編程愉快!