# 自己的第一个区块链——基于Python

By [leaf](https://paragraph.com/@leaf-6) · 2022-12-04

---

Python是一门简单易学、语法优美且功能强大的编程语言。它拥有一个自由开放的社区环境，该社区提供了诸如Web、爬虫、数据分析、机器学习等方面的开发框架和类库，可直接使用进行快速开发。另外，Python代码也被称为是可执行伪代码，好的Python代码就像伪代码一样，干净、简洁、一目了然，所以这里选择Python作为开发语言实现一个区块链原型。

Python是一种广泛使用的高级编程语言，由荷兰人由吉多·范罗苏姆发明，第一版发布于1991年。之所以选中Python作为这门开发语言的名字，是因为范罗苏姆是BBC电视剧《Monty Python》的爱好者，故选取了Python一词作为这门语言的名字。范罗苏姆对Python的定位是“优雅”“明确”“简单”，

所以Python程序语法简单易懂，即使是编程初学者，也能轻易上手，而且Python内置了丰富的数据结构和类库，即便是初学者也能通过Python轻松实现功能非常复杂的程序。

Python开发者的理念是：用一种方法，最好是只有一种方法来做一件事，这种理念深刻体现在Python程序的设计中。在开发Python程序时，如果面临多种选择，Python开发者一般会拒绝花哨的语法，而选择明确没有或者很少有歧义的语法。这些Python程序开发中的准则被称为“Python之禅”。Python开发者蒂姆·彼得斯对“Python之禅”的总结如下。

●Beautiful is better than ugly.美比丑好。

●Explicit is better than implicit.直言不讳比心照不宣好。

●Simple is better than complex.简单比内部复杂更好。

●Complex is better than complicated.内部复杂比外部复杂好。

●Flat is better than nested.平面的比嵌套的好。

●Sparse is better than dense.错落有致比密密匝匝的好。

●Readability counts.可读性很重要。

●Special cases aren’t special enough to break the rules.特殊情况不能特殊到打破规律。

●Although practicality beats purity.虽然实用比纯粹更重要。

●Errors should never pass silently.永远别让错误悄悄地溜走。

●Unless explicitly silenced.除非是你故意的。

●In the face of ambiguity，refuse the temptation to guess.碰到模棱两可的地方，绝对不要去作猜测。

●There should be one——and preferably only one——obvious way to do it.什么事情都应该有一个，而且最好只有一个显而易见的解决办法。

●Although that way may not be obvious at first unless you’re Dutch.一开始并不容易，因为你不是Python之父（这里Dutch指Python之父）。

●Now is better than never.现在就开始要比永远都不做好。

●Although never is often better than right now.很多时候永远都不做要比匆匆忙忙去做要好。

●If the implementation is hard to explain，it’s a bad idea.如果一个想法实现起来很困难，那它本身就不是一个好想法。

●If the implementation is easy to explain，it may be a good idea.如果一个想法实现起来很容易，那它或许就是一个好想法。

●NameSpaces are one honking great idea——let’s do more of those！名字空间是个了不起的想法，所以我们现在就开始吧。

这些准则让Python开发者倾向于注重简单，避免复杂，更加关注如何高效地解决问题，是值得每个开发者思考和遵循的。

下面让我们开始进入Python的世界，首先要做的是搭建一个Python开发环境。

1.Python安装

Python的安装过程比较简单，前往Python官网：https：//[www.python.org/downloads/下载对应操作系统的Python安装文件进行安装即可，Python安装文件的下载页面如示。](http://www.python.org/downloads/%E4%B8%8B%E8%BD%BD%E5%AF%B9%E5%BA%94%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%9A%84Python%E5%AE%89%E8%A3%85%E6%96%87%E4%BB%B6%E8%BF%9B%E8%A1%8C%E5%AE%89%E8%A3%85%E5%8D%B3%E5%8F%AF%EF%BC%8CPython%E5%AE%89%E8%A3%85%E6%96%87%E4%BB%B6%E7%9A%84%E4%B8%8B%E8%BD%BD%E9%A1%B5%E9%9D%A2%E5%A6%82%E7%A4%BA%E3%80%82)

1）选择Python代码编辑器

首先是选择一款合适的Python代码编辑器。

工欲善其事，必先利其器。虽然说用操作系统中自带的文本编辑器，比如记事本这种编辑器也可以用来编写Python代码，但这样的编辑器没有代码高亮、自动补全和代码错误提示等功能，会大大降低开发效率。所以，需要选择一款功能比较全面的Python代码编辑器，这样可以有效地提升开发效率和代码质量。这里对PyCharm、Sublime Text 3、Visual Studio Code、Atom、Jupyter Notebook等这几个主流的Python代码编辑器做一下简单比较，读者可结合自己的需求选择一款适合自己的编辑器。

●PyCharm，专门面向Python的全功能集成开发环境。不论是在Windows、Mac OS X系统中，还是在Linux系统中都可以快速安装和使用。PyCharm中集成了与Python开发相关的编辑、调试、代码管理等各项功能，打开一个新的文件就可以开始编写代码。PyCharm启动的时候稍微有点慢，比较适合开发和管理大型项目

Atom的下载地址是https：//atom.io/。

●Jupyter Notebook，是一款网页版的Python编辑器，可以在浏览器中编写和执行代码，执行结果会以HTML等富媒体格式进行展示。另外，Jupyter Notebook还支持使用LaTeX编写数学公式和使用Markdown编写文档。

Jupyter Notebook无须额外下载安装文件，只需要在终端（Linux和Mac OS）或命令行窗口（Windows）下使用Python包管理工具进行安装即可，安装命令如下：

pip install jupyter

本书将使用Jupyter Notebook进行Python代码的开发和调试。

介绍完编辑器后，再简单了解一下Python的包管理工具。

（2）安装Python包管理工具

目前最流行的Python包管理工具是pip命令，当使用Python安装文件安装完成后，pip也会一起安装到系统中。

![](https://storage.googleapis.com/papyrus_images/247719e32ca5553348119be8e4daeaa6ee7d31f8d2e247c277f6cf6ebd503fb3.png)

其他的命令可以使用pip­help进行查看，如图所示。

![](https://storage.googleapis.com/papyrus_images/96724398a5a5d8b01781ced0dd4c807176dae0aee7c87c313071eeea7c106d96.png)

介绍了包管理工具以后，再来了解一下Python的虚拟环境。

（3）创建Python虚拟环境

在Python的开发过程中需要安装第三方包和依赖库，而开发不同的项目需要安装的包和依赖库通常各不相同。为了防止不同的应用之间包和依赖库冲突，最好能创建一个独立的Python开发环境，也就是Python的虚拟环境。这样可以使每个项目的环境与其他项目独立开来，保证开发环境不受其他项目影响，解决包冲突的问题。而创建独立环境的方法就是使用虚拟环境管理工具。

当前最流行的Python虚拟环境管理工具叫virtualenv，它是Python的一个第三方工具。安装命令如下：

pip install virtualenv

安装完成后就可以创建一个独立的Python虚拟开发环境了。将这个虚拟环境命名为venv，创建命令是virtualenv venv -p python3，其中“-p python3”是指定python的版本为python 3。

![](https://storage.googleapis.com/papyrus_images/742254d3496b082ce80fd9d58e80e1f4c349abccc9f16ddd65bdd790803824c6.png)

要使用这个虚拟环境需要先用source venv/bin/activate命令将其激活，激活成功后会在当前行的最前面显示当前虚拟环境的名字

![](https://storage.googleapis.com/papyrus_images/32f7c518d32afe63b6e50a681f4078af3390cd946d5b74bc5b2e1f7e84ec3b6d.png)

至此，当前的Python开发环境就是前面创建的venv这个虚拟环境了。若想退出当前虚拟环境，可使用deactivate命令进行操作，退出后（venv）这个虚拟环境的名字也会消失，

4）Jupyter Notebook的启动和使用

下面在Python虚拟开发环境中安装jupyter并启动Jupyter Notebook，安装和启动Jupyter Notebook的命令及执行过程如图4-12和图4-13所示，启动命令是jupyter notebook。

![图4-12  安装jupyter](https://storage.googleapis.com/papyrus_images/20679627cf5d2573f7896f8663c0406ef4e3cbf35fa62d263fb1de40855f47d3.png)

图4-12 安装jupyter

![图4-13  启动Jupyter Notebook](https://storage.googleapis.com/papyrus_images/721f62149f4a931c2b753e03336c8af73c5ea745b1a262bb5ce956009e9bec4b.png)

图4-13 启动Jupyter Notebook

启动Jupyter Notebook后会自动在浏览器中打开一个页面（启动后程序默认监听8888端口），如图4-14所示。

![](https://storage.googleapis.com/papyrus_images/37f5b3daf4824a612f6210a288cc7eed8598d9abe4d14d8c14e0410df518e31b.png)

该页面显示的是“Files”标签下的内容，其中列出当前文件夹下的文件目录结构，第2个标签“Running”显示的是当前运行的notebook页面，第3个标签“Clusters”显示的是集群功能。要编写Python代码可以选择右上角的“New”菜单，在下拉框中选择“Python 3”来新建一个notebook页面，如图4-15所示。

![](https://storage.googleapis.com/papyrus_images/dd33a960f02242e62d3d13037255b92680b470040b2b0695deb0a6cc24391797.png)

新建的notebook页面主要可分为4块区域，名称、菜单栏、工具条和编辑区域，如图4-16所示。

![](https://storage.googleapis.com/papyrus_images/01e921de79551c6e48a03912c54c7a9ad1c5b4cfb6fedde967e36ecfe66b326b.png)

图4-16 Jupyter Notebook编辑页面

标题默认是“Untitled”，可以通过双击标题位置对其进行修改，菜单栏包括了所有对notebook的操作，工具条显示的是常用的命令。

下面开始编写第一个Python程序。

3.第一个Python程序

第一个Python程序的功能比较简单，就是输出“你好，区块链”这几个字，使用Python中的打印函数print函数实现，输入代码print（“你好，区块链”）后，单击“工具条”上的“Run”按钮或使用快捷键＜Shift+Enter＞执行代码，如图4-17所示，可以看到下面打印了结果“你好，区块链”。

![](https://storage.googleapis.com/papyrus_images/56f1169a2ff89e4ccf874311608f00b210ffaa894e6607fc206f9d821d673ed2.png)

区块包括区块头和区块体两个部分，区块头由版本、父区块哈希值、数据、Merkle根、时间戳、目标难度、Nonce值组成；区块体实际上可以包含任何内容，在比特币中包括交易输入数量、交易输出数量和长度不定的交易记录等信息。在以太坊中的区块体中除了交易数据还包含智能合约。为方便开发和理解，这里要开发的区块链系统简化了区块的结构，只使用最关键的几个字段，其他非必要的字段先忽略。简化后的区块包括父区块哈希值、数据、时间戳、哈希值这四个字段，区块的哈希值由区块中父区块哈希值、数据和时间戳这3个字段拼接起来通过哈希算法计算而成。通过Python定义区块的结构，如图

![](https://storage.googleapis.com/papyrus_images/b9a73e46f89f6325ba6b53d12bcbf2695aeb09aa2a05daaad68ddfc65f751710.png)

（2）定义区块链的结构

区块链是由区块组成的链条，定义了区块的结构后还需要定义一个区块链的结构。将各个区块通过哈希值前后依次相连，然后将这些区块都放到一个数组中，初始化时列表为空，新的区块依次放到这个列表中，再定义一个函数来实现向这个列表中添加区块的功能，从而定义了这个区块链的结构

![](https://storage.googleapis.com/papyrus_images/0d709b05c56b33f13bab96a93d3ddb2512880cb038b16066c6d692a1256a6d37.png)

区块链结构

以上就完成了最简单的区块链结构，下面在此基础上一步步对其完善直至实现一个真正的区块链系统。

（3）实现区块链原型

1）先来创建第1个区块，或者叫作创世区块，代码如图4-34所示，创世区块没有父区块，所以prev\_hash的值为空。

![](https://storage.googleapis.com/papyrus_images/ebd173dc36a254f9bb4342647686aa87236eed59f88aa37e090145956b5bfec0.png)

2）再创建两个区块，数据是关于张三的转账记录，prev\_hash依次是前一个区块的哈希值，如图

![](https://storage.googleapis.com/papyrus_images/b87dee798725700ef8f451c2487aee0d72aa17d88d382110c1a6fb1d59056d15.png)

3）然后新建一个区块链并将上面的区块添加到区块链中，如图4-36所示。

![](https://storage.googleapis.com/papyrus_images/445adf9568c660b1958ae0398833fc5727fbebfc43d88decfa9be90a79ce0189.png)

4）最后打印输出当前区块链的信息，可以看到这个区块链包含了3个区块，如图

![](https://storage.googleapis.com/papyrus_images/195a455d9565824e69a05eb4b530ab45da72d85fe7a3bd2000e1fe9440fecacf.png)

以上就实现了一个最简单的区块链原型，但它缺少了区块链的核心功能，比如共识机制、账户和交易、去中心化网络等，不能算是一个真正的区块链。接下来会在这个原型基础上一步步添加区块链的各种特性，直至实现一个完整的区块链。首先是为其加入共识机制—PoW（工作量证明）

共识机制是区块链技术的重要组成部分，在3.1共识机制中已经介绍过常见的几种共识机制。这里将使用Python实现比较简单的PoW，即工作量证明机制。PoW的原理是通过不断计算，直到找到一个随机数（Nonce）的值使得生成的哈希值满足一定的条件，如图

![](https://storage.googleapis.com/papyrus_images/dbdacc3be8fca82880d29ddc1529328b48a40e115585b378c0e8110d0afb515b.png)

下面我们将工作量证明机制加入上面的区块链原型中。

1）首先更新区块的结构，加入Nonce字段，如图

![](https://storage.googleapis.com/papyrus_images/5e71646113fdb5c92c5b0e4fe3d01ea0b940ac9368e82d8cfcb082782d1a863f.png)

2.哈希算法库

在Python中已经内置了一个哈希库——hashlib，它提供了常见的哈希算法，如MD5，SHA256等，以下为MD5和SHA256的使用方法，如图所示。

![](https://storage.googleapis.com/papyrus_images/ce397abf12a586650df84111c61aeeb33a185b5d556c594dfc017487f24b9734.png)

Base64是一种用64个字符来表示任意二进制数据的方法，Python中内置了Base64库以供使用，下面内容是将字符串“你好，区块链”进行Base64加密和解密的过程，代码如图

![](https://storage.googleapis.com/papyrus_images/bbad159de3d9f53ae01b15558fc6bf17b73d2e3f702be49841b8f7219bd8fa8b.png)

在Python中使用非对称加密算法，比如椭圆曲线算法时，需要安装第三方库。在此简单讲解如何在Python中使用椭圆曲线算法。要用椭圆曲线算法，需要先安装第三方库ECDSA，安装命令命令如下：

安装完成后先导入算法库，生成一对私钥和公钥，然后用私钥进行签名，用公钥进行签名验证，如图4-29所示，先使用SigningKey.generate（）方法生成一个私钥，由这个私钥可以生成一个唯一的公钥。然后使用私钥对“Something”这个字符串生成签名，而由私钥生成的公钥就可以用来验证这个签名是否正确

![](https://storage.googleapis.com/papyrus_images/3de1d2be2e71f7e98aab978f85e625e2b8c5279b0f65480d00756ce189cb123c.png)

最后介绍Python的绘图库Matplotlib，这个库用来生成常用的图表和进行数据可视化。Matplotlib支持各种平台，并且功能强大，能够轻易绘制出各种专业的图形。要使用这个库需要先进行安装，安装命令如下：

pip install matplotlib

Matplotlib库的使用方法如图

![](https://storage.googleapis.com/papyrus_images/279b128c18c7ede0549a40be166e74814e52dbd441dd72a4a48b11836ac88e79.png)

已详细介绍过区块的结构，区块包括区块头和区块体两个部分，区块头由版本、父区块哈希值、数据、Merkle根、时间戳、目标难度、Nonce值组成；区块体实际上可以包含任何内容，在比特币中包括交易输入数量、交易输出数量和长度不定的交易记录等信息。在以太坊中的区块体中除了交易数据还包含智能合约。为方便开发和理解，这里要开发的区块链系统简化了区块的结构，只使用最关键的几个字段，其他非必要的字段先忽略。简化后的区块包括父区块哈希值、数据、时间戳、哈希值这四个字段，区块的哈希值由区块中父区块哈希值、数据和时间戳这3个字段拼接起来通过哈希算法计算而成。通过Python定义区块的结构，如图

![](https://storage.googleapis.com/papyrus_images/b9a73e46f89f6325ba6b53d12bcbf2695aeb09aa2a05daaad68ddfc65f751710.png)

区块链是由区块组成的链条，定义了区块的结构后还需要定义一个区块链的结构。将各个区块通过哈希值前后依次相连，然后将这些区块都放到一个数组中，初始化时列表为空，新的区块依次放到这个列表中，再定义一个函数来实现向这个列表中添加区块的功能，从而定义了这个区块链的结构，具体代码如图

![](https://storage.googleapis.com/papyrus_images/0d709b05c56b33f13bab96a93d3ddb2512880cb038b16066c6d692a1256a6d37.png)

以上就完成了最简单的区块链结构，下面在此基础上一步步对其完善直至实现一个真正的区块链系统。

（3）实现区块链原型

1）先来创建第1个区块，或者叫作创世区块，代码如图4-34所示，创世区块没有父区块，所以prev\_hash的值为空。

![](https://storage.googleapis.com/papyrus_images/ebd173dc36a254f9bb4342647686aa87236eed59f88aa37e090145956b5bfec0.png)

2）再创建两个区块，数据是关于张三的转账记录，prev\_hash依次是前一个区块的哈希值，如图

![](https://storage.googleapis.com/papyrus_images/b87dee798725700ef8f451c2487aee0d72aa17d88d382110c1a6fb1d59056d15.png)

3）然后新建一个区块链并将上面的区块添加到区块链中，如图所示。

![](https://storage.googleapis.com/papyrus_images/445adf9568c660b1958ae0398833fc5727fbebfc43d88decfa9be90a79ce0189.png)

4）最后打印输出当前区块链的信息，可以看到这个区块链包含了3个区块，如图所示。

![](https://storage.googleapis.com/papyrus_images/195a455d9565824e69a05eb4b530ab45da72d85fe7a3bd2000e1fe9440fecacf.png)

几个重要的共识机制曾提到，在工作量证明的计算过程中，“挖矿”的计算量很大，而验证的方法很简单，计算量很小。从上图中的代码中也可以看到，“挖矿”消耗的时间为1.42s，而验证时间只需要0.026s（26μs），很明显验证比计算工作量证明简单得多。

4）完成工作量证明机制后，再生成一个新的加入工作量证明机制的区块链，如图所示。

![](https://storage.googleapis.com/papyrus_images/fdcbccb3fd94a80265e72e004815818895859885943cbab9b4b60b9a6d09797d.png)

将上述区块链的区块信息打印出来可以看到，区块的哈希值都是以“00000”开头，如图所示。

![](https://storage.googleapis.com/papyrus_images/df08d3f001f2f197208d268d110a9173157c791e5bc79585ab18778e62ed7040.png)

已经介绍过区块链技术中账户其实就是区块链网络中的一个地址，用来标示区块链网络中的某个结点。而钱包是用来存放账户的工具。账户的本质是一对唯一的私钥和公钥，也就是说钱包的本质是生成和管理这些密钥对的工具。以下是创建钱包、账户并实现其交易功能的步骤。

（1）创建钱包及账户

首先定义一个Wallet的类用来代表钱包，Wallet初始化时会生成一对唯一的私钥和公钥，即一个账户，生成的算法基于椭圆曲线算法，具体代码如图所示。

![](https://storage.googleapis.com/papyrus_images/448ac54d528ea0d482d0cfbce07b9cb270aa2e9d99955396f0cbfb89c5e5a9fa.png)

2）生成签名

创建账户后还需要提供这个账户的地址和公钥并利用账户的私钥生成签名。其中，地址由公钥先经哈希算法再进行Base64算法计算而成，签名生成的是一串二进制字符串，为便于查看，这里将这个二进制字符串转换成ASCII字符串进行输出，具体代码如图

![](https://storage.googleapis.com/papyrus_images/91ecfe8038f310fe938dd80e02579a9dffd9701f6af10e9d1ee17ac69f5a3801.png)

账户的地址、公钥和签名

（3）生成验证函数

然后还需要实现一个验证函数用来验证签名是否正确，生成验证函数的代码如图

![](https://storage.googleapis.com/papyrus_images/07f6de076049188580dcca204af219864ea76135f9c0f30bc26b6d7b96e13b8e.png)

（4）测试钱包的功能

接下来对钱包功能进行测试，包括：账户的生成、账户的地址、公钥信息以及签名功能是否正常，如图4-48所示。

基于上面的公钥和签名，可验证签名的正确性，如图所示，代码执行的返回结果为True，说明这个签名是基于和这个公钥配对的私钥生成的，也就是说钱包（账户）功能正常。

![](https://storage.googleapis.com/papyrus_images/c65bb8655fbbd6c1f4e76990f79217ac944044b7fe165472428cb43e5f1b141c.png)

![](https://storage.googleapis.com/papyrus_images/ce530c56cae5c79db59b9b67ae0e81ffdb02cb190e23ea8cc68fda2eaac0fd40.png)

钱包和账户的功能实现后还需要加入交易的功能。为了支持交易，这里需要再定义一个交易的数据结构。

在前面的步骤中，区块结构中的数据只是一个简单的字符串，而在实际的区块链中，数据是一个个的交易记录，这些交易记录需要包含交易的发送方、接收方、交易数量，以及用来验证交易的发送方公钥和发送方签名，这里定义一个包含这几个字段的Python类Transaction，如图

![](https://storage.googleapis.com/papyrus_images/d9e919b8839339369ed2cedab602dd9298281054b87b887fdbbeb466491b3686.png)

（6）整合钱包和交易功能

完成了钱包和交易的功能后，将这两个功能更新到前面完成的区块链原型中。

1）首先，将区块结构中的数据替换为交易列表，如图

![](https://storage.googleapis.com/papyrus_images/b0dcd04df22348ce5919f627adaadcfaf74298a1b5000d5cc6688011df8e4644.png)

2）接着，在工作量证明中添加奖励机制，这个奖励机制为挖矿成功后可以获得加密数字货币作为奖励。这里假定为每完成一个区块可获得1个加密数字货币，代码如图

![](https://storage.googleapis.com/papyrus_images/ee7a61fd42d50aac744d179dc62ab246ab5edbb25f901477538b1774d589f9ff.png)

3）然后，为了方便获取区块链中账号的加密数字货币情况，这里再添加一个名为get\_balance的查询函数，该函数会遍历整个区块链的交易数据，通过匹配账号获取与这个账号相关的所有交易记录，然后累加这个账号接收到的金额，并减去所有的支出金额，计算出该账户的当前余额，如图

![](https://storage.googleapis.com/papyrus_images/931fe4604546ec94353229ee22447f4028c006b715701cfc70f12bde3ad2092d.png)

7）测试区块链交易功能

最后来测试一下区块链的交易功能能否正常运行。先初始化一个空的区块链和3个钱包账户（alice、tom、bob），如图所示，可以看到初始化钱包中余额都是0。

![](https://storage.googleapis.com/papyrus_images/a35e890f6330bc0b04a33af416601aa3802e9243af0ba4586d135661dad3be05.png)

下面假设alice生成了创世区块并将创世区块添加到区块链中。根据工作量证明机制，alice将获得1个加密数字货币，代码

![](https://storage.googleapis.com/papyrus_images/432da841e78216fa15ad72d775f3b6d44015ef5c2a92dad395bf4edb14935b57.png)

alice获得奖励后可以向tom进行转账，这里转账了0.3个加密数字货币，代码如图

![](https://storage.googleapis.com/papyrus_images/6ce94e5606645ab5b8b8ab7eabd75f6ffb2c957d04a306786d2c74dc3ad62931.png)

假设这笔转账交易被广播到区块链网络后由bob进行验证并生成一个新的区块添加到了网络上，那么bob也将获得1个加密数字货币，代码如图

![](https://storage.googleapis.com/papyrus_images/87439552d5392fbb75f24194199f8f1528acfd7d3f8ab8c174e9ae182d50921a.png)

再次打印此时的钱包余额。此时可以看到alice转给tom 0.3个加密数字货币后变成了0.7个，tom因此拥有0.3个加密数字货币，bob挖矿获得了1个加密数字货币。如图

![](https://storage.googleapis.com/papyrus_images/274faec5bfc1cae6c7c8dd65396fceabd33b9c08e78f4079ce973b5a511885d1.png)

至此，这个区块链系统可以支持挖矿奖励和交易的功能了！但实际的区块链是运行在一个去中心化网络中的，在下一节中，将实现一个简单的去中心化的区块链网络。

---

*Originally published on [leaf](https://paragraph.com/@leaf-6/python)*
