学习有效的Python应用的数据结构

115 阅读14分钟

本教程包括:

  1. 什么是Python数据结构以及如何使用它们
  2. 如何实现一个由Python数据结构驱动的API
  3. 如何对你的Python API进行自动化测试

因为计算机依靠数据来执行指令,所以计算总是需要数据交互。在现实世界的应用中,数据量可能是压倒性的,所以开发者必须不断设计方法,以编程的方式快速有效地访问它。

对于专门开发工具和系统的团队来说,对数据结构的扎实理解是一个很大的优势。对数据进行优化组织可以最大限度地提高效率,使数据处理变得简单和无缝。在本教程中,你将学习Python中的数据结构,如何使用它们来构建高效和高性能的应用程序,以及如何使用持续集成为你的Python应用程序进行自动测试。

先决条件

要完成本教程,需要以下条件:

什么是数据结构?

数据结构是一种组织和管理内存中的数据的方法,以有效地执行对该数据的操作。使用不同的数据类型构建数据结构,以及定义保存数据的变量,都有最佳实践。

为什么需要Python数据结构?

随着系统复杂性的增加,数据也在增加。今天,处理大量的数据经常导致处理器速度的问题,搜索和排序数据的效率低下,还有处理多个用户请求时的问题。这些问题对性能至关重要,它们必须得到解决,以实现任何系统的最大效率。

数据结构被用来决定一个程序或一个系统如何运行。按顺序搜索数组中的数据将耗费大量的时间和资源。这可以通过使用数据结构(如哈希表)来解决。

数据抽象隐藏了数据结构的复杂细节,这样客户程序就不必知道实现细节。这是通过抽象数据类型完成的,它为你的应用程序提供了抽象性。

Python 中的数据结构概述

Python 提供了内置的数据结构,如列表、字典、集合和图元。Python 用户可以创建自己的数据结构并最终控制它们的实现方式。堆栈、队列、树、链表和图是用户定义的数据结构的例子。

本教程将重点介绍列表和字典,以及开发者如何使用它们来优化应用程序中的数据存储和检索。

下一节的重点是使用列表和字典的优化操作来存储、处理和检索数据结构中的数据。

列表

列表是一个元素的有序集合。因为列表是可变的,它们的值可以改变。项目是包含在一个列表中的值。

注意Python 数据结构的类型决定了它的可变性。可变的对象可以改变其状态或内容,而不可变的对象不能。

你通过使用方括号来表示 Python 列表。这里是一个空列表的例子。

categories = [ ]

逗号 (,) 用来分隔列表中的项目。

categories = [ science, math, physics, religion ]

列表也可以包含属于列表的项目。

scores = [ [23, 45, 60] , [67, 69, 90] ]

index ,它只不过是一个列表中数值的位置,是用来访问列表中的元素。下面是一个如何访问列表中各种项目的例子。

categories = [ science, math, physics, religion ]

输出

categories [0]  # science
categories [1]  # math
categories [2]  # physics

你也可以使用负数索引从列表的末尾开始访问项目。例如,要访问前面列表中的最后一个项目。

categories [-1] # religion

你可以添加、删除和修改列表中的项目,因为列表是可变的。

要改变列表中某一项目的值,需要引用该项目的位置,然后使用赋值运算符。

categories [ 0 ] = “geography” # modifies the lists, replacing “science” with “geography”

要向列表中添加新的项目,可以使用append() 方法,该方法将项目添加到列表的末尾。

categories .append( “linguistics” )

另一个可以在列表中使用的方法是insert() ,它在列表中的一个随机位置添加项目。其他列表对象包括del(),pop(),clear(), 和sort()

字典(Dictionaryaries

字典是 Python 中内置的key-value 对数据类型的集合。与列表不同,字典是由键来索引的,它可以是字符串、数字或图元。一般来说,一个字典的键可以是任何不可变的类型。

一个 dictionary 的 key 必须是不同的。大括号{} 是用来表示字典的。

键使字典的工作变得简单,也可以存储各种类型的数据,包括列表或甚至其他字典。你可以使用字典的键来访问、删除和执行其他操作。关于字典,需要记住的一件重要事情是,用一个已经存在的键来存储数据将覆盖以前与该键相关的值。

下面是一个使用字典的例子。

student = { “name”: “Mike”, “age”: 24, “grade”: “A” }

要访问上述字典内的项目。

student[ ‘name’ ] # Mike

向 dictionary 中添加数据就像 Dict[key] = value 一样简单。

student[ ‘subjects’ ] = 7

Python 的字典方法包括len(),pop(),index(),len(), 和popitem()

下面这些常用的方法允许它从 dictionary 中返回值。

dict.items()     # return key-value pairs as a tuple
dict.keys()      # returns the dictionary's keys
dict.get(key)  # returns the value for the specified key and returns None if the key cannot be found.

下图显示了不同类型的 Python 数据结构,包括内置的和用户定义的。

Python data structures

在本教程的下面一节中,你将使用刚刚获得的知识来创建一个简单的 API,允许你存储、操作和检索数据结构。

API流程图和存储

现在你知道什么是列表和字典了,你可以用它们来创建一个具有登录功能的API端点,数据只存储在数据结构中。你可以观察数据将如何在应用程序中流动以及你将如何在你的API中使用数据结构。

Python API data flows

这个API图显示了一个数据存储,它是一个Python字典。它被初始化为样本用户数据,因为这允许你充分探索数据结构的能力。步骤被标记为14 ,以显示数据在API中的流动。

第一步让用户通过输入他们的first namelast nameusernamedate of birth 来创建账户。这些细节被保存在users 字典中。

第二步是检索系统中的所有用户。在发回数据之前,它被从一个嵌套的字典转换为一个排序的列表。

第三步用一个Id 和一个username 来验证一个用户。

第四步是数据结构在向客户端发回响应之前进行实际处理。

现在你知道了你的API将如何工作,你可以把数据结构放到一个实际的应用程序中。

用数据结构实现一个API

使用数据结构实现包括以下步骤。

  • 设置一个API骨架
  • 初始化用户
  • 创建用户
  • 检索用户

设置API骨架

为了继续学习本教程,我鼓励你克隆该应用程序。这样,你就可以浏览应用程序,了解本教程中没有完全记录的部分。

git clone https://github.com/CIRCLECI-GWP/python-api-with-datastructures

cd python-api-with-datastructures

为了安装Python依赖项,你需要使用这些命令建立一个虚拟环境。

Windows操作系统

py -3 -m venv venv;

venv\Scripts\activate;

Linux/MacOS

python3 -m venv venv

source venv/bin/activate

安装requirements.txt文件中的要求。

pip install -r requirements.txt

要启动API,请运行。

python main.py

设置和启动API骨架的工作非常出色下一步是修改你的路由,并创建一个链接列表来处理用户认证和数据转换。

初始化用户

考虑到你的应用状态只在服务器运行时持续,你将创建一个用户字典,用样本数据进行初始化。要做到这一点,在main.py 文件中手动添加数据到用户的字典中,就在Flask应用配置之后。

这就是修改后的字典应该有的样子。

# main.py

users = {
   1: {"fname": "John", "lname": "Doe", "username": "John96", "dob":    "08/12/2000"},
   2: {
       "fname": "Mike",
       "lname": "Spencer",
       "username": "miker5",
       "dob": "01/08/2004",
   },
}

现在,即使你的服务器停止了,在你测试端点或创建新的应用程序数据时,你将始终有内存中的数据可以参考。

创建用户

在初始化了用户数据字典之后,制作一个create user 函数来创建你的用户。使用request库,因为这将是一个API请求,而用户凭证将通过提交的方式进入。

使用请求库中的get_json() 方法--data = request.get_json() --来解析传入的JSON请求数据,并将其存储在一个变量中。任何系统都不应该允许重复的记录,你的API也不例外。因此,当创建一个新用户时,要确保新用户的详细信息与任何可用的记录不匹配。如果相同的数据已经可用,则通知用户并停止该过程。复制这段代码并将其粘贴到main.py 文件中。

# main.py
@app.route("/user", methods=["POST"])
def create_user():

   data = request.get_json()

   if data["id"] not in users.keys():
       users[data["id"]] = {
           "fname": data["fname"],
           "lname": data["lname"],
           "username": data["username"],
           "dob": data["dob"],
       }
   else:
       return jsonify({"message": "user already exists"}), 401

   return jsonify({"message": "user created"}), 201

这个代码块首先确定用户id ,通过在用户字典的键中搜索类似的id,确定用户是否已经被存储在用户数据存储中。检查是否有id ,这不是程序性的;相反,你可以在生产中检查用户的电子邮件。

如果该检查通过,新的用户信息就被输入到字典中,使用唯一的用户 ID 作为键。当字典针对用户id存储时,这种模式会产生一个嵌套的字典。

Flask包含一个叫jsonify 的函数,允许你将数据序列化为JSON格式,你将用它来格式化发回客户端的信息。

检索用户

检索用户可以像返回users 字典一样简单,但有一个更好的方法。相反,为什么不按降序返回所有的用户,把最近创建的用户放在最上面?

不幸的是,字典在 Python 3 中不再是可排序的,所以它们不能被排序。相反,你可以使用这个片段。

# main.py
@app.route("/users", methods=["GET"])
def get_users():

   all_users = []

   for key in users:
       all_users.append(users[key])
       users[key]["id"] = key

   all_users.sort(key=lambda x: x["id"], reverse=True)

   return jsonify(users), 200

这在前面的代码块中创建了一个空列表,然后循环浏览users 的字典值,把每个值追加到列表中。另外,每个用户都需要一个唯一的标识符,所以在列表中追加一个id 是一个好主意。

记住,在追加到列表之后,你有一个字典的列表,你不能通过将你的嵌套字典转换为字典的列表来欺骗 Python。这就是为什么你应该使用 lambda 函数来指定 id 作为排序方法的一个键。结果是一个按用户的id 值降序排序的字典列表。

最后,添加认证功能--/user/login ,在创建用户并实现按顺序检索用户的函数后,将是非常好的。

# main.py
app.route("/users/login", methods=["POST"])
def login_user():

   data = request.get_json()

   id = data["id"]
   username = data["username"]

   if id in users.keys():
       if users[id]["username"] == username:
           return jsonify(f"Welcome, you are logged in as {username}"), 200

   return jsonify("Invalid login credentials"), 401

在使用idusername ,通过比较发出的id和记录,确保这样的用户存在。如果存在匹配,你可以验证用户名。如果一个用户输入了有效的登录信息,就把他们登录进去,并显示一个带有他们用户名的欢迎信息。相反,如果登录失败,将简单地显示一条消息,通知他们登录失败。

开始测试你刚刚创建的三个端点。create a user,log them in, 和retrieve all users added 。如果有什么问题,你可以随时参考位于克隆版本库中的main.py 文件。

创建用户的API调用

Creating a user

登录用户的API调用

User login

检索所有用户的API调用

Retrieving all users

利用在列表和字典中存储数据的能力,你可以验证API是否按预期工作。

为你的API编写测试

没有经过测试的代码已经被破坏了 可能很乏味,也很耗时,但在一个应用程序中添加测试从来没有真正的损失。本教程的这一部分包括对用户创建、多用户创建、登录和用户检索你刚刚创建的API端点的测试。我将指导你使用Pytest ,一个Python应用测试工具来测试你的端点。你要写的第一个测试是创建一个用户的测试。

# tests/test_app.py
def test_create_user(client):

    response = client.post(
        "/user",
        json={
            "id": 4,
            "fname": "James",
            "lname": "Max",
            "username": "Maxy",
            "dob": "08/12/2000",
        },
    )

    assert response.headers["Content-Type"] == "application/json"
    assert response.status_code == 201

这段代码创建了一个新的用户,ID为4 ,名字为James ,姓氏为Max 。然后,它断言响应的内容类型是JSON,状态代码是201 ,是一个创建的资源。

接下来创建一个测试来验证该测试可以获取创建的用户。


def test_fetch_users(client):

    response = client.get("/users")

    assert response.headers["Content-Type"] == "application/json"
    assert response.status_code == 200

这个测试验证端点返回一个JSON响应,状态代码是200 ,表示请求成功。这两个测试只是一个开始;在在根目录下的文件tests/test_app.py ,还有更多的测试。通过从命令行运行pytest 来执行你的测试。

Successful PyTest execution

测试的通过验证了从Python数据结构中创建的API端点的行为与使用实际数据库的API端点的行为是一样的。

现在你的测试在本地通过了,把它们与你的持续集成环境整合起来,以确保部署到GitHub仓库的变化不会破坏应用程序。在本教程的这一部分,我们将使用CircleCI作为CI环境。

与CircleCI集成

要将CircleCI配置添加到您的项目中,请在您的项目文件夹的根部创建一个新的目录,名为.circleci 。在该目录中,创建一个名为config.yml 的文件。将此配置添加到.circleci/config.yml 文件中。

version: 2.1
orbs:
  python: circleci/python@1.5.0
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.10.2
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
      - run:
          name: Run tests
          command: pytest
workflows:
  sample:
    jobs:
      - build-and-test

这个CircleCI配置是一个简单的例子,说明如何配置CircleCI来运行你的测试。它指定你正在使用一个Python Docker镜像,然后使用pip 包管理器安装Python包,并使用pytest 命令运行你的测试。

设置CircleCI

现在你在远程main GitHub分支上有了代码,你可以设置CircleCI来运行你的测试。进入CircleCI仪表板,选择项目标签。在列表中找到你的存储库。对于本教程,它是python-api-with-datastructures 仓库。

Project repository

选择设置项目的选项。因为你已经把你的CircleCI配置推送到了远程仓库,你可以直接输入包含配置的分支名称,然后点击设置项目

Configuring CircleCI

坐下来,看着你的测试在CircleCI中执行。

Successfull CI test execution

你的测试成功通过了,这只意味着一件事:是时候庆祝一下了!

总结

通过跟随本教程的学习,你已经对Python数据结构有了扎实的了解,为什么需要它们,特别是如何在Python中使用列表和字典数据结构。你还学会了只使用数据结构来编写端点。你为你的API端点写了测试,以避免破坏现有的变化。你学会了如何集成CircleCI,并观察了CircleCI在CI平台上执行你的测试。