简体中文
  • python
  • 编程
  • 学习
  • sdk

在一个周末学会 Python:从零开始完成一个项目

我们怎样才能快速学习一门新的编程语言呢?在这篇文章中,我们将分享我们通过构建一个完整的项目在周末学会 Python 的经历。

Gao
Gao
Founder

介绍

作为一个身份服务,Logto 提供跨多种编程语言和框架的流畅体验至关重要。这通常涉及到创建软件开发工具包 (SDK)。然而,当编程语言不在我们的技术堆栈之内,且团队缺乏相关经验时,打造一个健壮的 SDK 就成了一个挑战。

Python 对我们来说就是一个这样的挑战。尽管有很多 Python 用户,但我们缺乏一个 Python SDK,这是一个挥之不去的担忧。决心解决这个问题之后,我开始填补这一空白。

尽管我有多年的编程经验,Python 对我来说仍然是一个相对陌生的领域。虽然我多年前曾短暂接触过 Python 2 进行简单脚本编写,但我的知识已经过时了。然而,现在是深入学习的时候了!

第一天:打下基础

确定目标

根据我的经验,学习一门新编程语言最有效的方法是构建一个完整的项目。幸运的是,我们的目标很明确:构建一个用于 Web 应用的 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 等效方案”,并找到了一个出色的候选人 Poetry

  • 它不仅是一个包管理器,也是一个依赖管理器和虚拟环境管理器。
  • 它有一个 pyproject.toml 文件,类似于 Node.js 中的 package.json
  • 它使用一个锁定文件来记录精确的依赖版本。

现代命令行工具通常包含一个 init 命令,专为新项目而设计。Poetry 也一样。我运行了该命令,它为我创建了一个 pyproject.toml 文件。

第一行代码

终于,写代码的时刻到了。以经典的“Hello, World!” 程序开始总是一个好的选择。在学习一门编程语言时,功能齐全的集成开发环境 (IDE) 并非总是必要的;一个备受社区支持的编辑器,例如 VS Code,完全足够。

考虑到我们的 SDK 侧重于 Web 应用,我从使用流行框架 Flask 编写一个简单的 Web 服务器开始。

利用 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 版本使用统计”,我决定使用 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。如果你有任何建议,请不要犹豫告诉我们。

希望本文对学习一门新编程语言有所帮助。快乐黑客!