在一個週末學習 Python:從零開始到完成一個項目
我們如何能夠快速學習一門新的編程語言?在這篇文章中,我們將分享通過建立一個完整項目來學習 Python 的週末經驗。
介紹
作為身份服務的 Logto,在各種編程語言和框架中提供流暢的體驗是至關重要的。這通常涉及創建軟件開發工具包(SDK)。然而,當編程語言不在我們的技術堆棧中,而我們的團隊對其不熟悉時,製作一個穩健的 SDK 變成了一個挑戰。
對我們來說,Python 就是這樣一個挑戰。儘管擁有許多 Python 用戶,我們卻缺乏一個 Python SDK,這一直令人擔憂。決心解決這個問題,我開始填補這個空白。
雖然我有多年的編程經驗,但對我而言,Python 還是一片相對未知的領域。儘管多年前我曾為了寫簡單的腳本而簡單接觸過 Python 2,我的知識卻過時了。不過,是時候開始深入研究了!
第一天:打好基礎
定義目標
根據我的經驗,學習新編程語言的最有效方法是構建一個完整的項目。幸運的是,我們的目標很明確:為網絡應用程序構建一個 Logto Python SDK。
與其急於編寫代碼,我們先分解它。以下是清單:
- 創建 Logto 客戶端,用於登錄、登出、用戶信息和令牌管理等任務。
- 提供教程和樣例項目以展示 SDK 的使用。
- 將 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 的經驗,"回調地獄" 這個短語可能讓你印象深刻。這是一個常見問題,當涉及到異步操作時。現代編程語言提供類似的解決方案,例如 Promise
和 coroutine
。
幸運的是,Python 有一個內置方案 async
和 await
。在使用它們之前,確保與受歡迎框架的兼容性。在 Flask 中,可以通過安裝 async
擴展並使用 async def
來取代 def
:
然後我們可以使用 await
來等待異步操作的結果。
HTTP 請求
HTTP 請求是一個有趣的話題。幾乎每種編程語言都有一個本地解決方案,但開發者通常使用第三方庫以便使用更方便。幾個例子:
- JavaScript:
XMLHttpRequest
對fetch
對axios
- Swift:
URLSession
對Alamofire
- Java:
HttpURLConnection
對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 版本使用統計”,我決定以 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。如有任何建議,請隨時告訴我們。
希望這篇文章對學習新編程語言有所幫助。祝編程愉快!