繁體中文(香港)
  • python
  • programming
  • learning
  • sdk

在一個週末學習 Python:從零開始到完成一個項目

我們如何能夠快速學習一門新的編程語言?在這篇文章中,我們將分享通過建立一個完整項目來學習 Python 的週末經驗。

Gao
Gao
Founder

Stop wasting weeks on user auth
Launch secure apps faster with Logto. Integrate user auth in minutes, and focus on your core product.
Get started
Product screenshot

介紹

作為身份服務的 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。如有任何建議,請隨時告訴我們。

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