EDUCBA-数据分析高级-Python-笔记-全-

EDUCBA 数据分析高级 Python 笔记(全)

001:包与模块导论 🧩

在本节课中,我们将要学习Python中包与模块的核心概念。这是进行高级数据分析与物联网应用开发的基石。我们将了解什么是模块、如何导入它们,以及为什么这些工具对于处理和分析来自物联网设备的数据至关重要。

在上一模块的物联网基础Python课程中,我们学习了变量、循环和函数等基础知识。现在,我们将进入更高级的主题。本模块不面向初学者,而是为那些已经具备一定Python基础,并希望深入学习其在物联网中应用的学员准备的。

为什么需要高级Python与模块? 🔍

当我们谈论物联网的高级学习时,必须掌握诸如模块等概念。这些工具使我们能够利用他人编写好的、功能强大的代码,从而高效地构建复杂应用。

物联网应用通常涉及从各种传感器和控制器收集大量数据。这些数据需要被处理、分析,并可能存储或转发。为了有效地完成这些任务,我们需要借助专门的Python模块。

本模块将涵盖的核心内容 📚

以下是本模块计划讲解的主要知识点:

  • 标准内置模块:例如 mathdatetimestatistics。这些是Python自带的工具库。
  • 第三方数据分析模块:这是数据分析的核心,我们将重点学习:
    • numpy:用于高效的数值计算。
    • scipy:用于科学计算。
    • pandas:用于数据处理和分析。
    • matplotlib:用于数据可视化。
  • 核心编程概念:虽然属于Python核心,但对于构建健壮的物联网应用至关重要:
    • 异常处理:使用 try...except 块来优雅地处理程序运行中可能出现的错误。
    • 多线程:使用 threading 模块来同时执行多个任务。例如,在树莓派上同时等待按钮按下和发送网络信息。
  • 数据库模块:为了存储数据,我们将学习如 sqlite3mysql-connector 等模块。
  • 开发工具:我们将介绍新的集成开发环境,例如 Jupyter NotebookPyCharm,以提升我们的开发体验。

总结 🎯

本节课中,我们一起学习了高级Python模块在物联网数据分析中的重要性。我们概述了本课程将要涵盖的核心内容,包括标准库、强大的第三方数据分析包(如numpypandas)、确保程序稳定运行的异常处理与多线程技术,以及用于数据持久化的数据库连接模块。掌握这些工具和概念,将为你构建高效、可靠的物联网数据处理系统打下坚实基础。

数据分析高级Python:构建与优化:P02-01:Anaconda发行版概念 🐍

在本节课中,我们将要学习数据分析所需的Python环境搭建,重点介绍Anaconda发行版及其核心概念,并简要了解我们将使用的集成开发环境。

Anaconda是一个集成了Python、Jupyter Notebook以及众多数据分析必备软件包的发行版。它极大地简化了环境配置过程,使我们能够快速开始数据分析工作。对于数据科学领域,Anaconda被广泛且迅速地采用。

什么是Anaconda发行版?

上一节我们提到了数据分析的环境需求,本节中我们来看看Anaconda如何满足这些需求。

Anaconda是一个发行版,它捆绑了Python解释器、Jupyter Notebook以及我们将要使用的大量核心软件包。这意味着像NumPy、SciPy、Matplotlib、Pandas等库在安装Anaconda时就已经预装好了,无需我们单独安装,这对初学者非常友好。

如何下载与安装Anaconda?

以下是下载Anaconda的步骤:

  1. 访问Anaconda官方网站:https://www.anaconda.com
  2. 点击“Download”按钮,进入下载页面。
  3. 根据你的操作系统(Windows、macOS或Linux)选择对应的安装程序。
  4. 对于Windows用户,建议下载Python 3.6版本、64位的图形化安装程序(如果你的电脑是64位系统)。
  5. 下载完成后,运行安装程序(通常是一个.exe文件)。
  6. 安装过程非常简单,只需在图形界面中不断点击“Next”即可完成。

安装完成后,Anaconda发行版就部署好了,我们可以开始使用其中的各种应用,例如Jupyter Notebook。

另一个IDE:PyCharm

除了Jupyter Notebook,我们还会使用另一个集成开发环境(IDE)——PyCharm。PyCharm由JetBrains公司开发,功能强大,尤其在版本控制等方面能提供很大帮助。

以下是获取PyCharm的说明:

  • 社区版:这是一个免费的开源版本,完全满足科学计算和数据分析的需求。
  • 专业版:这是一个付费版本,提供了更多面向Web开发等专业领域的特性。

对于本课程的学习,下载并安装社区版就足够了。其安装过程与Anaconda类似,下载安装程序后按照指引完成即可。

环境概览与后续安排

至此,我们已经了解了核心的环境组件。我们将在后续视频中详细讲解具体的设置步骤。目前,我们已经可以准备就绪,进入编程环节。我们将主要使用两种工具:Jupyter NotebookPyCharm

本节课中我们一起学习了Anaconda发行版的概念、其预装软件包的优势、以及如何下载安装Anaconda和PyCharm。这些工具为我们后续的数据分析Python编程打下了坚实的基础。

003:PyCharm与Anaconda安装与配置 🛠️

在本节课中,我们将学习如何安装和配置两个重要的Python开发工具:PyCharm集成开发环境和Anaconda数据科学平台。我们将完成从下载到基本使用的全过程。

概述

我们将同时安装PyCharm Community Edition和Anaconda。PyCharm是一个功能强大的Python IDE,适合构建和管理复杂的项目。Anaconda则是一个集成了众多数据科学库和Jupyter Notebook的Python发行版。我们将学习如何配置它们并了解其基本用法。


安装PyCharm Community Edition

首先,我们从官方网站下载PyCharm Community Edition的安装程序并启动它。

以下是安装步骤:

  1. 运行安装程序,选择“Community Edition”版本。
  2. 在安装选项中,确认选择64位版本,然后点击“Next”。
  3. 选择创建程序快捷方式等选项,接受默认设置即可。
  4. 点击“Install”开始安装过程。

安装程序将自动完成PyCharm的安装。与此同时,我们也将并行安装Anaconda。


安装Anaconda

在PyCharm安装的同时,我们启动Anaconda的安装程序。

以下是安装步骤:

  1. 运行Anaconda安装程序,选择自定义安装位置或使用默认路径。
  2. 确保不覆盖任何旧版本,以避免冲突。
  3. 完成安装后,我们可以在开始菜单中找到Anaconda Navigator和Anaconda Prompt。

至此,PyCharm和Anaconda都已安装完毕。接下来,我们将学习如何使用它们。


配置与使用PyCharm

安装完成后,我们首次运行PyCharm Community Edition。启动时,我们可以跳过初始设置,直接进入主界面。

为了配置Python解释器,我们需要进行以下操作:

  1. 进入“File”菜单,选择“Settings”。
  2. 在设置窗口中,找到“Project Interpreter”选项。
  3. 在这里,我们可以查看和选择已安装的Python解释器,例如Python 3.6。

配置完成后,我们可以创建一个新项目。

以下是创建新项目的步骤:

  1. 点击“Create New Project”。
  2. 为项目命名,例如“Advanced_Python_For_DA”。
  3. 选择项目的存储位置,例如桌面或特定文件夹。
  4. 点击“Create”完成项目创建。

现在,我们可以在项目中创建Python文件并运行代码。例如,创建一个名为test.py的文件,输入print(“Hello World”),然后使用快捷键Ctrl+Shift+F10运行它。


使用Jupyter Notebook

Anaconda安装完成后,我们可以通过Anaconda Navigator或开始菜单启动Jupyter Notebook。Jupyter Notebook是一个基于Web的交互式计算环境,非常适合数据分析和教学。

让我们了解Jupyter Notebook的基本用法:

  1. Jupyter Notebook启动后,会在浏览器中打开文件浏览器界面。我们可以导航到之前为PyCharm项目创建的文件夹。
  2. 点击“New”按钮,选择“Python 3”来创建一个新的Notebook文件。
  3. 在出现的代码单元格中,我们可以编写和执行Python代码。例如,输入print(“Hello World”),然后按Shift+Enter运行该单元格。
  4. Jupyter Notebook的优势在于可以分段执行代码。我们可以在不同的单元格中分别编写代码,并独立运行它们。例如,在一个单元格中定义变量a=4, b=5,在下一个单元格中计算a+b,变量值会在整个Notebook会话中保持。
  5. 除了代码单元格,我们还可以插入Markdown单元格来添加文本说明、标题和文档。例如,使用#创建标题,编写课程描述等。

这种将代码、输出和文档说明结合在一个文件中的方式,使得分析和展示工作流程变得非常清晰。


工具间的协作使用

在数据科学项目中,我们经常需要结合使用PyCharm和Jupyter Notebook。

以下是它们各自的典型用途:

  • PyCharm:更适合构建结构化的项目,例如需要多个模块、文件夹和复杂包管理的应用程序开发。
  • Jupyter Notebook:更适合进行探索性数据分析、快速原型设计、数据可视化和制作可重复的研究报告。

在本课程中,我们将根据任务的需要,灵活地交替使用这两种工具。PyCharm帮助我们构建稳健的项目框架,而Jupyter Notebook则助力我们进行快速的数据探索和交互式编程。


总结

本节课中,我们一起完成了PyCharm和Anaconda的安装与基本配置。我们学习了如何在PyCharm中创建项目、配置解释器并运行Python脚本。同时,我们也探索了Jupyter Notebook的核心功能,包括创建笔记本、分段执行代码以及使用Markdown添加文档。理解这两种工具的定位和基本操作,为我们后续的数据分析高级Python编程打下了坚实的基础。

004:在PyCharm中执行程序

在本节课中,我们将学习如何在PyCharm中执行Python程序,并对Python的核心概念进行一次快速回顾。我们将从基础的“Hello World”程序开始,然后复习Python中的几种主要数据结构及其基本操作。


概述

本节课程旨在帮助您熟悉PyCharm环境,并快速回顾Python编程的核心概念。我们将从执行一个简单的程序开始,然后复习列表、元组、字典和集合这四种基本数据结构。这些知识是后续学习更高级数据分析库(如NumPy和Pandas)的基础。


Python基础快速回顾

上一节我们介绍了课程目标,本节中我们来看看Python的一些核心概念。虽然假设您已具备Python基础,但这里将对一些关键点进行简要回顾。

数据结构简介

Python中有四种主要的内置数据结构,它们分别是:列表(List)、元组(Tuple)、字典(Dictionary)和集合(Set)。这些结构是构建更复杂数据操作的基础。

列表操作示例

列表是使用方括号 [] 定义的可变序列。以下是列表的一些基本操作:

# 定义一个列表
LST = [0, 1, 2, 3]
print(LST)
print(type(LST))

您可以通过索引访问列表中的元素,索引从0开始:

# 访问列表元素
print(LST[2])  # 输出索引为2的元素

以下是遍历列表的两种方法:

# 迭代方法1:直接遍历元素
print("迭代方法1:")
for element in LST:
    print(element)

# 迭代方法2:通过索引遍历
print("迭代方法2:")
for i in range(len(LST)):
    print(LST[i])

列表有多种方法可以修改其内容。以下是几个常用方法:

  • append(): 在列表末尾添加一个元素。
  • extend(): 将另一个列表中的所有元素添加到当前列表末尾。

让我们通过代码看看它们的区别:

# 使用append方法
LST.append(4)
print(LST)  # 输出: [0, 1, 2, 3, 4]

# 使用append添加另一个列表(会作为单个元素嵌套进去)
LST.append([5, 6])
print(LST)  # 输出: [0, 1, 2, 3, 4, [5, 6]]

# 使用extend方法添加另一个列表的元素
LST.extend([7, 8])
print(LST)  # 输出: [0, 1, 2, 3, 4, [5, 6], 7, 8]

如您所见,append() 会将整个参数作为一个元素添加,而 extend() 则会展开参数中的元素并逐个添加。


总结

本节课中我们一起学习了如何在PyCharm中运行Python程序,并对Python的基础数据结构——列表进行了快速回顾。我们了解了列表的定义、元素访问、两种遍历方式以及 append()extend() 等关键方法。掌握这些基础知识对于后续学习更高级的数据分析工具至关重要。在接下来的课程中,我们将以此为基础,探索元组、字典、集合以及NumPy和Pandas等库中的高级数据结构。

005:列表方法与其他数据结构 🐍

在本节课中,我们将学习Python列表的更多实用方法,并快速了解元组、集合和字典这三种基本数据结构。掌握这些知识是进行高效数据分析的基础。

上一节我们介绍了列表的基本操作,本节中我们来看看列表的其他内置方法。

列表的其他方法

列表对象提供了多种方法来修改和查询数据。以下是几个关键方法:

  • insert 方法:在列表的指定索引位置插入一个新元素。其语法为 list.insert(index, value)
    • 例如,LST.insert(2, 15) 会在索引2的位置插入数值15。

  • pop 方法:移除列表中的一个元素。默认移除并返回最后一个元素。其语法为 list.pop()list.pop(index)
    • 例如,LST.pop() 移除最后一个元素。
    • 例如,LST.pop(-3) 移除从末尾开始计数的第3个元素(即索引为 len(LST)-3 的元素)。

  • sort 方法:对列表本身进行升序排序。其语法为 list.sort()

  • reverse 方法:将列表中的元素反向排列。其语法为 list.reverse()

此外,Python还有一些内置函数可以直接作用于列表:

  • sum 函数:计算列表中所有数值元素的总和。其语法为 sum(list)
  • len 函数:获取列表的长度(元素个数)。其语法为 len(list)


列表推导式

列表推导式提供了一种简洁、高效的方式来创建新列表。其核心思想是对一个序列中的每个元素应用一个表达式或进行过滤。

其基本语法结构为:

新列表 = [对元素的表达式 for 元素 in 旧列表 if 条件]

例如,将一个列表中的每个元素求平方:

SQ_list = [element**2 for element in LST]

列表推导式在数据分析中非常实用,例如进行单位转换。假设有一个摄氏温度列表,需要将其转换为华氏温度:

temp_celsius = [20, 22, 23, 24, 25, 26, 27, 25]
temp_fahrenheit = [t * 9/5 + 32 for t in temp_celsius]

这个例子展示了如何通过一行代码完成整个列表的批量计算。


元组、集合与字典简介

了解列表后,我们快速浏览一下Python中其他几种重要的数据结构。

元组

元组使用圆括号 () 定义,例如 T = (5, 2, 8, 4)。元组与列表的主要区别在于元组是不可变的,创建后不能修改其元素。尝试执行 T[0] = 10 会导致错误。元组常用的方法只有 count()index()

集合

集合使用花括号 {} 定义,例如 S = {1, 2, 3, 5, 5, 5}。集合的核心特性是元素唯一且无序。打印集合 S 会得到 {1, 2, 3, 5},重复的5被自动去重。由于无序,不能通过索引访问集合元素。集合常用于成员检测、去重以及数学运算(如并集、交集)。

字典

字典使用花括号 {} 定义,但其元素是 键: 值 对,例如 D = {25: ‘Roy‘, 50: ‘Noop‘, 80: ‘Re‘}。字典的键相当于自定义的索引,可以通过键来快速访问对应的值,例如 D[25] 会返回 ‘Roy‘。字典在存储和查询具有映射关系的数据时非常高效。


本节课中我们一起学习了Python列表的进阶方法,掌握了强大的列表推导式技巧,并快速了解了元组、集合和字典这三种基本数据结构的特点与用途。这些是构建复杂数据处理流程的基石,在后续学习NumPy、Pandas等数据分析库时将会频繁用到。

006:构建自定义类

在本节课中,我们将学习如何构建自定义类。类是创建自定义数据结构的核心工具,它允许我们将数据(属性)和操作数据的方法(函数)封装在一起。我们将通过创建一个物联网设备管理类来理解其基本概念和用法。

概述

上一节我们介绍了Python的基础数据结构。本节中,我们来看看如何创建自己的数据结构——类。我们将学习如何定义类、初始化对象、添加属性和方法,以及使用类属性来跟踪对象数量。

创建类与 __init__ 方法

要创建自定义类,需要使用 class 关键字。__init__ 方法是一个特殊方法,每当创建该类的一个新对象(实例)时,它会被自动调用。它的主要作用是对新创建的对象进行初始化。

以下是创建一个名为 IoTDevices 的类的语法:

class IoTDevices:
    def __init__(self):
        print("一个 IoTDevices 类的对象已被创建。")

当创建对象时,__init__ 方法会自动执行:

rasppy = IoTDevices()
# 输出:一个 IoTDevices 类的对象已被创建。

为对象添加属性

__init__ 方法中,我们可以接收参数并将其赋值给对象的属性。属性是绑定到特定对象上的变量,可以通过 self.属性名 来访问和存储。

以下是定义带有属性的 __init__ 方法的示例:

class IoTDevices:
    def __init__(self, name, device_type, cost):
        self.name = name
        self.type = device_type
        self.cost = cost
        print(f"一个 {self.name} 设备对象已被创建。")

创建对象并访问其属性:

device1 = IoTDevices("Raspberry Pi", "控制器", 3500)
print(f"设备名称: {device1.name}")
print(f"设备成本: {device1.cost}")

为类添加方法

方法是定义在类内部的函数,用于描述对象可以执行的操作。方法的第一个参数通常是 self,它代表调用该方法的对象实例。

以下是为 IoTDevices 类添加一个 print_details 方法的示例:

class IoTDevices:
    def __init__(self, name, device_type, cost):
        self.name = name
        self.type = device_type
        self.cost = cost

    def print_details(self):
        print(f"设备名称是 {self.name},类型是 {self.type},成本是 {self.cost}。")

使用方法:

device1 = IoTDevices("Raspberry Pi", "控制器", 3500)
device2 = IoTDevices("数字万用表", "仪器", 500)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/25dda7d56f1773d8f2190fc7f575ca7e_60.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/25dda7d56f1773d8f2190fc7f575ca7e_61.png)

device1.print_details()
device2.print_details()

使用类属性与静态方法

类属性是属于类本身的变量,被所有实例共享。静态方法使用 @staticmethod 装饰器定义,它不依赖于任何特定的对象实例,可以通过类名直接调用。

以下是使用类属性和静态方法来跟踪创建了多少个设备的示例:

class IoTDevices:
    # 类属性,用于计数
    __count = 0  # 双下划线开头表示私有变量

    def __init__(self, name, device_type, cost):
        self.name = name
        self.type = device_type
        self.cost = cost
        IoTDevices.__count += 1  # 每创建一个对象,计数加1

    def print_details(self):
        print(f"设备名称是 {self.name},类型是 {self.type},成本是 {self.cost}。")

    @staticmethod
    def get_count():
        """静态方法,返回已创建的对象数量"""
        return IoTDevices.__count

使用静态方法获取计数:

device1 = IoTDevices("Raspberry Pi", "控制器", 3500)
print(f"当前设备数量: {IoTDevices.get_count()}")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/25dda7d56f1773d8f2190fc7f575ca7e_81.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/25dda7d56f1773d8f2190fc7f575ca7e_83.png)

device2 = IoTDevices("数字万用表", "仪器", 500)
print(f"当前设备数量: {device2.get_count()}")  # 也可以通过对象调用

总结

本节课中我们一起学习了Python中类的构建。我们掌握了如何通过 class 关键字定义类,使用 __init__ 方法初始化对象属性,以及如何为类添加实例方法和静态方法。我们还了解了类属性的概念,并学会了使用私有变量和静态方法来管理类级别的数据。通过创建 IoTDevices 这个自定义类,你现在已经能够构建属于自己的数据结构来组织和处理数据了。

007:客户端服务器的网络层面 🖧

在本节课中,我们将要学习网络编程的核心概念,特别是在物联网等场景中至关重要的客户端-服务器模型。我们将通过日常生活中的例子来理解这个模型,并了解它如何支撑起像WhatsApp这样的现代通信应用。

概述

网络在编程中扮演着重要角色,尤其是在物联网领域。许多设备通过互联网或本地网络连接在一起,并相互通信。当我们谈论“网络”时,可能首先想到的是社交网络,如WhatsApp、Facebook或LinkedIn。但在计算机领域,网络指的是通过某种接口或中介,将世界各地的两台计算机或设备连接起来。

从消息传递理解网络

为了理解网络,我们以消息传递或聊天为例。这涉及从一台计算机或设备向另一台发送信息。

假设用户XYZ在他的手机上使用WhatsApp,向他的朋友ABC发送了一条“hi”消息。这条消息从XYZ的手机发送到ABC的手机,并显示在ABC的WhatsApp应用里。这里产生一个问题:这两部手机是直接连接在一起的吗?实际上,它们并非直接相连。那么它们连接到了谁?

想象一部智能手机通过Wi-Fi连接到互联网,另一台笔记本电脑也连接到互联网。XYZ可能通过手机应用登录,而ABC可能使用WhatsApp网页版在笔记本电脑上接收消息。两者都通过互联网连接,但最终都需要与一个中心实体通信。

引入客户端-服务器模型

网络中最流行的模型之一就是客户端-服务器模型。这个模型类似于店主与顾客的关系。

  • 店主(服务器)只有一个,他拥有丰富的资源(如货物、数据)。
  • 顾客(客户端)可以有很多个,他们向店主提出请求(例如,想买什么商品)。
  • 店主收到请求后,向顾客提供响应(例如,交付商品)。

在计算机术语中:

  • 服务器 是一个中心节点,拥有资源(如数据、处理能力),并等待接收请求。
  • 客户端 是向服务器发起请求以获取服务或资源的节点。

它们之间的关系可以表示为:

客户端 --(请求)--> 服务器
服务器 --(响应)--> 客户端

模型的工作方式

以下是客户端-服务器模型的关键工作流程:

  1. 多个客户端连接到服务器,但客户端之间不直接连接。
  2. 当客户端需要数据或服务时,它会向服务器发送请求。
  3. 服务器处理请求,并从其资源中获取或生成所需的数据。
  4. 服务器将响应发送回发起请求的特定客户端。

以WhatsApp为例

现在,让我们将这个模型应用到WhatsApp的消息传递中:

  1. 用户XYZ(客户端1)发送消息“hi”。
  2. 这条消息首先被发送到WhatsApp的服务器。
  3. 服务器收到消息后,可能会向XYZ发送一个确认响应(例如,“消息已送达”)。
  4. 同时,服务器知道这条消息需要传递给用户ABC(客户端2)。
  5. 服务器将消息“hi”推送给在线的ABC客户端。
  6. ABC在他的设备上收到消息。

关键在于,两个用户(客户端)并没有直接连接。他们是通过WhatsApp服务器这个中介进行通信的。服务器管理了所有的连接和消息路由。这就是流行的客户端-服务器模型的工作方式。

总结

本节课中,我们一起学习了网络编程的基础——客户端-服务器模型。我们通过店主与顾客的类比理解了服务器(资源提供者)和客户端(服务请求者)的角色。并以WhatsApp为例,看到了该模型如何在实际应用中运作,使得设备能够通过一个中心服务器进行间接通信,而非直接相连。理解这个模型是构建和理解大多数网络应用的基础。

008:客户端与服务器的消息通信 💬

在本节课中,我们将学习客户端与服务器之间如何进行消息通信。这是理解网络应用,特别是物联网(IoT)应用如何工作的基础。我们将通过一个简单的比喻来解释消息传递的流程,并探讨它在实际场景中的应用。

消息传递流程详解

上一节我们介绍了网络通信的基本概念,本节中我们来看看一个具体的消息传递模型,它类似于我们日常使用的即时通讯工具。

当客户端发送一条消息时,它会收到一个单勾(✔️)。这个单勾表示消息已成功发送到互联网,并被服务器接收。但这不意味着接收方客户端已经收到了消息,它只表示消息已抵达中央服务器。

当接收方客户端上线时,服务器会将消息转发给它。一旦接收方客户端成功收到消息,它会通知服务器。服务器随后会向原始发送方客户端发送一个双勾(✔️✔️)确认,表示接收方已收到消息。

如果接收方客户端进一步打开并阅读了消息,它会再次通知服务器。服务器则会向发送方客户端发送蓝色双勾(🔵✔️✔️)确认,表示消息已被阅读。

以下是消息状态变化的完整流程:

  1. 消息发送:客户端A发送消息到服务器。
  2. 服务器确认:服务器接收消息,并向客户端A返回单勾(✔️)。
  3. 消息投递:接收方客户端B上线,服务器将消息转发给它。
  4. 接收确认:客户端B接收消息,服务器向客户端A返回双勾(✔️✔️)。
  5. 阅读确认:客户端B阅读消息,服务器向客户端A返回蓝色双勾(🔵✔️✔️)。

这就是客户端-服务器模型的基本工作流程。服务器作为位于外部的中央枢纽,负责协调两个客户端之间的通信。这是网络通信中广泛使用的流行模型。

物联网(IoT)应用实例

理解了基本模型后,我们来看看它在物联网领域的具体应用。在这个场景中,客户端通常是嵌入在设备中的智能硬件。

在我们的物联网应用中,设备(如ESP8266、树莓派或其他联网设备)会连接到互联网。这些设备可以从各种传感器收集大量外部环境数据,或执行某些计算任务。

设备会定期向服务器更新数据。让我们以一个真实的用例来说明:假设我们将一个物联网控制器安装在一台洗衣机中。

这个控制器(即客户端)负责监控洗衣机的各种状态。以下是它可能收集的数据示例:

  • 洗衣机的开机与关机时间。
  • 电机启动的时间与状态。
  • 洗衣机是否正常工作。
  • 水温是否达到预设水平。

因为这些设备连接到了互联网,它们会将数据发送到服务器。服务器随后将这些数据存储到数据库中。

数据监控与请求响应

数据存储在服务器后,如何被查看呢?这时会出现另一种客户端:数据监控方。

例如,对于三星或惠而浦的洗衣机,公司的维护人员可能需要监控全美所有洗衣机的运行状态。他可以通过浏览器(如Chrome)登录一个管理平台。

当他在浏览器中输入网址(例如 www.educba.com)并按下回车时,就向EDUCBA的服务器发送了一个请求。同样,他也可以向洗衣机数据服务器发送请求,询问“我想查看这台洗衣机的数据”。

服务器在收到请求后,会从数据库(存储了来自洗衣机客户端的数据)中提取相关信息。接着,服务器将这些数据组织成网页格式(使用HTML、CSS、JavaScript等技术),并以HTTP响应的形式发送回浏览器。

最终,这个响应会以网页的形式显示在维护人员的屏幕上,使他能够实时监控洗衣机的状态。

这里存在两种不同的客户端:

  1. 设备客户端:位于洗衣机内部,负责收集和发送数据。
  2. 用户客户端:位于浏览器中,负责向服务器请求并查看数据。

总结

本节课中我们一起学习了客户端-服务器通信模型的核心流程,包括消息发送、接收和阅读确认的状态变化。我们还探讨了该模型在物联网领域的实际应用,区分了作为数据生产者的设备客户端与作为数据消费者的用户客户端。理解这种双向通信机制是构建高效、可监控的网络化数据分析应用的基础。

009:服务器IP与端口从客户端到服务器 🖥️➡️📱

在本节课中,我们将要学习客户端与服务器之间如何通过网络进行通信。核心在于理解两个关键概念:IP地址端口号。我们将通过一个简单的比喻来解释这些概念,并说明它们如何协同工作,以确保消息能够准确送达目标设备上的特定应用程序。


上一节我们介绍了客户端-服务器模型的基本概念。本节中我们来看看,当两个客户端(例如两个WhatsApp用户)想要通过一个中央服务器进行通信时,具体是如何实现的。

一个常见的问题是:这个客户端如何通过服务器与另一个客户端对话?假设两个WhatsApp客户端想要彼此交流。它们都连接到互联网,但需要通过服务器进行通信。

那么它们如何对话呢?一个连接到互联网的客户端需要与另一个客户端通信,必须通过一个特定的地址来识别对方。这就好比你想从家里给商店打电话订货,而不是亲自去商店。你需要知道商店的电话号码才能联系店主。

现在问题来了:我如何联系店主?联系店主的唯一方式是知道他的电话号码。我拨打他的号码,他接听电话。然后他如何知道送货地址?因为我告诉了他我的地址。所以,我有地址(我的位置),他有电话号码(他的联系方式),这使得我们能够相互联系。

同样地,在网络通信中,如果一个客户端需要与服务器对话,这意味着客户端必须知道服务器的IP地址。当服务器需要与另一个客户端对话时,服务器也必须知道那个客户端的IP地址。因此,双方都需要知道彼此的IP地址才能通过互联网通信。

例如,用户ABC通过WhatsApp向用户XYZ发送消息“Hi”。消息首先发送到WhatsApp服务器。然后,服务器需要将这条消息发送到XYZ的手机。XYZ的手机连接到互联网,因此它有一个IP地址。理论上,服务器可以通过这个IP地址将消息送达XYZ的手机。

但是,这里出现了一个关键问题:IP地址只能确保消息到达设备(手机)。我们如何确保这条消息被WhatsApp应用程序接收,而不是被手机上的Facebook或其他应用程序接收?这显然是不对的,消息应该送达目标设备上的WhatsApp客户端。

因此,IP地址仅足以将数据包送达目标设备。如果你希望数据到达设备上的某个特定应用程序,则需要比IP地址更多的信息。这个额外的信息就是端口号

所以,网络通信需要两样东西:

  1. IP地址:用于定位目标设备。
  2. 端口号:用于标识设备上特定的应用程序或服务。

以下是端口号的一个例子:当你访问Google时,你实际上是在向Google服务器发送请求。Google服务器默认运行在端口80上。即使你在浏览器地址栏中输入 www.google.com 而不指定端口,浏览器默认也会使用端口80(HTTP协议的标准端口)。这就是为什么你能正常访问网页。

如果你尝试访问 www.google.com:8080,你将无法得到响应,因为Google服务器并不在8080端口上监听请求。它只在端口80上工作。

所以,服务器通常会在特定的端口上“监听”请求。对于常见的Web服务(HTTP),这个端口通常是80。


当我们理解了客户端-服务器模型后,可以明确通信需要两个要素:IP地址端口号。当然,随着我们后续的编程实践,我们会更深入地理解这些细节。

本节课中我们一起学习了网络通信的基础。我们了解到,仅仅有IP地址只能确保数据到达正确的设备,而要确保数据被正确的应用程序接收,则必须借助端口号。IP地址和端口号共同构成了网络通信的完整地址,就像电话号码和具体收件人姓名共同确保包裹送达正确的人手中一样。在接下来的学习中,我们将看到如何在Python程序中应用这些概念来建立网络连接。

010:网络编程基础 🖧

在本节课中,我们将学习网络编程的核心概念,并动手创建一个简单的客户端-服务器模型。我们将了解套接字(Socket)的工作原理,以及客户端和服务器端编程的基本步骤。


创建项目结构

首先,我们为物联网项目创建一个网络编程相关的包和文件。

在项目文件中,创建一个名为 networking 的包。在该包内,创建两个Python文件:client.pyserver.py。这两个文件将分别用于编写客户端和服务器端的代码。


客户端编程步骤 📡

上一节我们创建了项目结构,本节中我们来看看客户端编程的具体步骤。客户端代码的编写主要包含四个清晰的步骤。

以下是客户端编程的四个核心步骤:

  1. 创建套接字
    首先,需要创建一个套接字。套接字是网络通信的端点,它由 IP地址端口号 共同标识。可以将其理解为一个在特定设备端口上运行、并连接到互联网的应用程序。

  2. 连接到服务器套接字
    创建套接字后,客户端需要将其连接到目标服务器的套接字上,以建立通信链路。

  3. 进行通信
    连接建立后,客户端和服务器就可以开始相互通信了,例如发送和接收数据。

  4. 关闭套接字
    通信结束后,客户端应关闭其套接字,释放网络资源。


服务器端编程步骤 🖥️

了解了客户端的工作流程后,本节我们来看看服务器端。服务器端的步骤稍多一些,因为它需要持续监听并处理来自多个客户端的连接请求。

以下是服务器端编程的六个核心步骤:

  1. 创建套接字
    与客户端一样,服务器首先也需要创建一个套接字。

  2. 绑定套接字到端口
    服务器需要将其套接字绑定到一个特定的端口号上。这类似于商店选择一个固定的营业地址,方便客户前来访问。例如,可以将服务器绑定到端口 808080

  3. 监听连接
    绑定端口后,服务器必须开始监听是否有客户端尝试连接。就像开店后,需要等待顾客上门。

  4. 接受客户端连接
    当有客户端发起连接时,服务器需要接受这个连接。接受连接意味着在客户端和服务器之间建立了一条持续的通信通道,双方可以借此进行对话。

  5. 进行通信
    连接建立后,服务器就可以通过这个连接与客户端进行数据交换(发送和接收)。服务器可能需要同时处理多个连接,就像餐厅有多个服务员同时为不同桌的客人服务一样。

  6. 关闭连接与服务器
    与单个客户端的通信结束后,关闭该活动连接。处理完所有客户端请求后,最终可以关闭服务器套接字。


总结

本节课中我们一起学习了网络编程的基础。我们明确了客户端编程的四个步骤:创建套接字 -> 连接服务器 -> 通信 -> 关闭套接字。同时也掌握了服务器端编程的六个步骤:创建套接字 -> 绑定端口 -> 监听 -> 接受连接 -> 通信 -> 关闭。理解这些步骤是构建任何网络应用的基础。在接下来的实践中,我们将把这些步骤转化为具体的Python代码。

011:编写客户端与服务器端代码 🖥️

在本节课中,我们将学习如何使用Python的socket模块编写一个简单的客户端与服务器端程序,实现网络通信。我们将从创建套接字开始,逐步完成连接、数据发送与接收等核心步骤。

概述

网络编程的核心是客户端与服务器之间的通信。Python的socket模块提供了实现这种通信所需的所有工具。本节将引导你编写一个基础的客户端和服务器端脚本,让它们能够在同一台机器上通过指定的端口进行对话。


导入Socket模块

编写客户端或服务器端代码的第一步是导入socket模块。这个模块包含了创建和管理网络连接所需的所有类和方法。

import socket


创建套接字对象

上一节我们导入了socket模块,本节中我们来看看如何创建套接字对象。套接字是网络通信的端点。

socket.socket()函数用于创建一个新的套接字对象。无论是客户端还是服务器端,都需要创建这样一个对象。

client_socket = socket.socket()
server_socket = socket.socket()

服务器端:绑定与监听

创建服务器端套接字后,需要将其绑定到一个具体的IP地址和端口上,并开始监听来自客户端的连接请求。

以下是服务器端需要执行的关键步骤:

  1. 绑定地址:使用bind()方法将服务器套接字绑定到本地主机的特定端口(例如1234)。
  2. 开始监听:使用listen()方法使服务器套接字进入监听状态,等待客户端连接。
  3. 接受连接:使用accept()方法接受一个客户端的连接请求。此方法会返回一个新的套接字对象(用于与该特定客户端通信)和客户端的地址。
# 绑定到本地主机的1234端口
server_socket.bind(('localhost', 1234))
print("服务器已绑定到端口号 1234")

# 开始监听
server_socket.listen()
print("服务器正在监听客户端连接...")

# 接受一个客户端连接
connection, address = server_socket.accept()
print(f"收到来自地址 {address} 的客户端连接")

客户端:连接到服务器

客户端套接字创建后,需要主动连接到服务器。

以下是客户端需要执行的关键步骤:

  1. 连接到服务器:使用connect()方法,指定服务器的地址(如localhost)和端口号(如1234),发起连接。
# 连接到本地主机上的服务器,端口为1234
client_socket.connect(('localhost', 1234))

发送与接收数据

连接建立后,客户端和服务器就可以通过各自的套接字对象进行数据交换。需要注意的是,网络传输的数据必须是字节(bytes)格式,而不是字符串。

以下是数据交换的流程:

  1. 客户端发送数据:客户端使用sendall()方法发送数据。在发送前,需要将字符串使用encode()方法编码为字节。
  2. 服务器接收数据:服务器使用recv()方法接收数据。接收到的数据是字节格式,需要使用decode()方法解码为字符串。

# 客户端发送消息
message = "你好,服务器!我是客户端。"
client_socket.sendall(message.encode())

# 服务器接收消息
data = connection.recv(1024) # 1024是缓冲区大小
decoded_message = data.decode()
print(f"从客户端接收到的消息:{decoded_message}")

关闭连接

通信完成后,必须正确关闭套接字以释放系统资源。

以下是关闭连接的步骤:

  1. 关闭用于数据通信的活跃连接(服务器端由accept()返回的connection对象)。
  2. 关闭主服务器套接字和客户端套接字。
# 客户端关闭连接
client_socket.close()

# 服务器端关闭连接
connection.close()
server_socket.close()

总结

本节课中我们一起学习了网络编程的基础。我们使用Python的socket模块,逐步实现了客户端与服务器端的代码。关键步骤包括:导入模块、创建套接字、服务器绑定与监听、客户端发起连接、进行字节格式的数据收发,以及最后关闭所有连接。虽然这个例子运行在同一台机器上,但其原理完全适用于不同机器间的网络通信。

012:套接字编程服务器端

在本节课中,我们将学习如何运行一个简单的服务器端套接字程序,并处理端口冲突问题。我们还将探讨如何实现服务器与客户端之间的双向通信,并理解为实现实时双向通信而引入多线程的必要性。

运行服务器程序

上一节我们介绍了套接字编程的基础概念,本节中我们来看看如何实际运行一个服务器程序。

运行服务器代码时,可能会遇到端口已被占用的问题。错误信息通常为“only one usage of each socket address is normally permitted”。这意味着指定的端口(例如1234)已被其他进程使用。

以下是解决此问题的步骤:

  1. 停止当前正在使用该端口的进程。
  2. 更改服务器程序绑定的端口号,例如改为2000。
  3. 为了便于管理,可以创建一个变量(如 port_number)来存储端口号,并在代码中所有需要的地方引用此变量。

修改并保存代码后,再次运行服务器。此时控制台应显示“server is bound to port number 2000”和“server is listening for clients”,表明服务器已成功启动并在指定端口监听客户端连接。

实现客户端连接与消息接收

服务器启动后,客户端可以尝试连接。当客户端连接到服务器的2000端口时,服务器端会接收到连接。

服务器端会执行以下操作:

  • 打印出客户端的地址和端口号(例如 127.0.0.1:6804)。
  • 接收客户端发送的消息(例如“hi server I'm a client”)。
  • 将接收到的字节数据解码为字符串并打印出来。
  • 关闭与当前客户端的连接。
  • 关闭服务器套接字。

实现服务器到客户端的消息发送

目前通信是单向的(客户端到服务器)。为了实现双向通信,我们需要让服务器也能向客户端发送消息。

在服务器关闭连接和套接字之前,可以添加发送数据的代码。使用连接对象(conn)的 sendall() 方法,并确保发送的数据是字节格式。

代码示例:服务器发送消息

conn.sendall(b"Message from server")

相应地,客户端在发送消息后,需要等待并接收来自服务器的回复。使用客户端套接字对象的 recv() 方法接收数据(例如1024字节),然后解码并打印。

代码示例:客户端接收消息

data = client_socket.recv(1024)
print(data.decode())

按照此流程,服务器和客户端可以完成一次“发送-接收”的交互。但需要注意的是,在当前代码逻辑下,双方不能同时进行发送和接收操作。

引入多线程实现实时双向通信

上述简单的“一问一答”模式无法满足需要持续、同时进行发送和接收的应用场景(如聊天应用)。这里就出现了一个问题:程序需要能够同时处理发送和接收数据。

为了实现客户端既能发送数据也能接收数据,或者服务器能同时处理多个客户端的请求,两个操作需要能够同时发生。这要求客户端套接字的 recv()send() 方法能并发执行。

这需要引入一个称为 多线程 的概念。我们需要理解多线程的工作原理,学习如何让多个任务同时执行,并在此基础上构建网络通信程序。掌握多线程后,我们就可以朝着开发一个真正的聊天应用迈进,实现用户之间的实时对话。

本节课中我们一起学习了如何运行和调试基本的服务器端套接字程序,实现了简单的双向消息传递,并认识到为实现更复杂的实时通信功能,需要引入多线程技术。这是构建交互式网络应用(如聊天工具)的关键一步。

013:Python多线程工具包 🧵

在本节课中,我们将要学习如何使用Python的threading模块来创建和管理多线程。多线程允许程序同时执行多个任务,这对于构建需要并发处理的应用(如聊天服务器)至关重要。

创建多线程包与文件

首先,我们需要创建一个新的Python包和文件来组织我们的多线程代码。

创建一个名为multithing的新Python包。

在该包内,创建一个新的Python文件,命名为multi_thread_one.py

导入线程模块

multi_thread_one.py文件中,我们需要从Python的threading模块导入Thread类。这个类用于创建线程对象。

from threading import Thread

threading模块导入Thread类,用于创建多线程对象。

定义线程目标函数

接下来,我们需要定义一个函数,作为线程启动后要执行的任务。

import time

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/2e359917ca32b442a304f3b180fb0a47_22.png)

def f1():
    print("Hi from F1")
    time.sleep(5)
    print("Hi F1 woke up")

这个f1函数会先打印一条消息,然后休眠5秒钟,最后打印另一条消息。

创建并启动线程

现在,我们可以创建一个Thread对象,并将f1函数设置为其目标。然后启动这个线程。

t1 = Thread(target=f1)
t1.start()

t1.start()会启动线程,并开始执行f1函数中的代码。

主程序的并发执行

当线程启动后,主程序并不会等待线程结束,而是继续执行自己的代码。这演示了并发执行。

print("Hi from Main")
time.sleep(5)
print("Hi Main got up")
t1.join()

主程序打印消息、休眠,然后使用t1.join()等待线程t1执行完毕,最后程序结束。

运行这段代码,你会看到来自线程和主程序的输出交错出现,这证明了它们是在同时执行的。

创建多个线程并传递参数

上一节我们介绍了如何创建单个线程,本节中我们来看看如何创建多个线程并向目标函数传递参数。

以下是创建多个线程的步骤:

  1. 使用循环创建多个Thread对象。
  2. 通过args参数向目标函数传递数据。
def f1(arg):
    print("Hi from F1 with argument " + str(arg))
    time.sleep(5)
    print("Hi F1 woke up with argument " + str(arg))

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/2e359917ca32b442a304f3b180fb0a47_32.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/2e359917ca32b442a304f3b180fb0a47_33.png)

for i in range(10):
    t = Thread(target=f1, args=(i,))
    t.start()

在这段代码中,我们创建了10个线程,每个线程都执行f1函数,并传入一个不同的i值作为参数。由于线程的执行顺序由操作系统调度决定,因此每次运行的输出顺序可能不同。

多线程在聊天应用中的应用

我们学习了多线程的基本概念。这个技术将被用于构建一个聊天应用,其中客户端与服务器需要同时进行发送和接收消息。多线程使得这种双向同时通信成为可能。

本节课中我们一起学习了Python多线程的基础知识,包括如何导入模块、创建线程、定义目标函数以及实现并发执行。我们还了解了如何创建多个线程并传递参数,为后续构建需要并发处理的网络应用(如聊天服务器)打下了基础。

014:与聊天机器人协作 🤖

在本节课中,我们将学习如何使用Python的socketthreading模块构建一个简单的聊天机器人客户端与服务器。我们将通过创建两个独立的脚本来实现网络通信,并利用多线程技术实现同时发送和接收消息。


创建项目结构

首先,我们需要为聊天机器人应用创建一个新的项目包。我们将创建两个主要的Python文件:一个用于客户端,另一个用于服务器。

以下是创建项目结构的步骤:

  1. 创建一个名为chatbot的新包。
  2. 在该包内,创建两个Python文件:
    • client.py:用于实现聊天客户端。
    • server.py:用于实现聊天服务器。


构建客户端

上一节我们创建了项目结构,本节中我们来看看如何构建客户端。

客户端的主要任务是连接到服务器,并能够同时发送和接收消息。这需要用到socket模块来建立网络连接,以及threading模块来并行处理收发操作。

以下是客户端client.py的核心实现步骤:

  1. 导入模块:导入socketthreading模块。
  2. 创建套接字:使用socket.socket()创建一个客户端套接字对象。
  3. 连接服务器:使用client_socket.connect()方法连接到运行在本地主机(localhost)端口2000上的服务器。
  4. 定义收发函数
    • receive_data():一个循环,持续从服务器接收数据(client_socket.recv(1024)),解码后打印到控制台。
    • send_data():一个循环,持续从用户输入获取消息,编码后发送给服务器(client_socket.sendall())。
  5. 启动线程:创建两个线程,分别以receive_datasend_data函数为目标,并启动它们,实现同时收发。

核心代码示例

import socket
import threading

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/663685fc9be55e006de7cce1f6541c52_24.png)

def receive_data(client_socket):
    while True:
        data = client_socket.recv(1024)
        print(f"来自服务器的消息: {data.decode()}")

def send_data(client_socket):
    while True:
        user_input = input("请输入要发送的消息: ")
        client_socket.sendall(user_input.encode())

# 创建并连接套接字
client_socket = socket.socket()
client_socket.connect(('localhost', 2000))

# 创建并启动线程
receive_thread = threading.Thread(target=receive_data, args=(client_socket,))
send_thread = threading.Thread(target=send_data, args=(client_socket,))
receive_thread.start()
send_thread.start()

构建服务器

了解了客户端的构建后,本节我们来构建服务器端。

服务器的任务是绑定到一个端口,监听客户端的连接请求,并为每个连接处理消息的收发。与客户端类似,服务器也需要使用多线程来处理并发的通信。

以下是服务器server.py的核心实现步骤:

  1. 导入模块:导入socketthreading模块。
  2. 创建并绑定套接字:使用socket.socket()创建服务器套接字,并使用server_socket.bind(('', port))将其绑定到指定的端口(例如2000)。
  3. 监听连接:调用server_socket.listen()开始监听传入的连接。
  4. 接受连接:在循环中调用server_socket.accept()接受客户端连接。该方法返回一个新的连接对象(conn)和客户端地址。
  5. 为连接创建处理线程:对于每个接受的连接,创建两个线程:
    • 一个线程用于从该连接接收数据(conn.recv()),解码后打印或处理。
    • 另一个线程用于向该连接发送数据(conn.sendall()),数据可以来自服务器控制台输入或其他逻辑。
  6. 启动线程:启动这些线程,使服务器能够与该客户端进行全双工通信。

核心代码示例

import socket
import threading

def handle_client(conn, addr):
    print(f"新连接来自: {addr}")
    # 为每个客户端连接创建收发线程(逻辑与客户端类似)
    # receive_thread = threading.Thread(target=receive_from_client, args=(conn,))
    # send_thread = threading.Thread(target=send_to_client, args=(conn,))
    # receive_thread.start()
    # send_thread.start()

# 主服务器循环
server_socket = socket.socket()
port = 2000
server_socket.bind(('', port))
server_socket.listen()
print(f"服务器已在端口 {port} 启动,等待连接...")

while True:
    conn, addr = server_socket.accept()
    client_thread = threading.Thread(target=handle_client, args=(conn, addr))
    client_thread.start()

总结

本节课中我们一起学习了如何使用Python构建一个基础的聊天机器人通信框架。我们掌握了以下核心技能:

  1. 使用socket模块创建网络套接字,实现客户端与服务器之间的TCP连接。
  2. 理解客户端-服务器模型,包括客户端的connect操作和服务器端的bindlistenaccept操作。
  3. 运用threading模块创建多线程,使得程序能够同时执行发送和接收消息的任务,实现了实时双向通信。
  4. 掌握了网络数据传输中编码(encode)与解码(decode 的必要性,以确保文本信息能正确通过字节流传输。

通过将上述概念组合在client.pyserver.py两个脚本中,我们搭建了一个可以同时处理多个消息流的简单聊天系统原型。这是开发更复杂网络应用(如在线聊天室或分布式数据处理节点)的重要基础。

015:发送与接收数据 🚀

在本节课中,我们将学习如何构建一个能够同时发送和接收数据的网络客户端与服务器。我们将使用Python的socketthreading模块来实现双向通信。

上一节我们介绍了网络连接的基础,本节中我们来看看如何让程序同时处理发送和接收任务。

核心概念与流程

为了实现同时收发数据,我们需要使用多线程。一个线程负责持续接收数据,另一个线程(通常是主线程)负责处理用户输入并发送数据。以下是实现这一功能的核心步骤:

  1. 客户端:启动一个后台线程来持续接收服务器发来的消息,同时主线程循环等待用户输入并发送给服务器。
  2. 服务器:同样启动一个线程来持续接收客户端发来的消息,同时主线程循环等待管理员输入并发送给客户端。

关键点在于,接收数据的操作需要在一个无限循环while True)中进行,以确保能持续监听消息。

客户端实现详解

以下是客户端代码的核心结构。

首先,我们需要导入必要的模块并建立连接。

import socket
from threading import Thread

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/950b01b82fc0fefb02fba9ebc2e77c0a_13.png)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 9999))

接下来,我们定义接收数据的函数。这个函数将在单独的线程中运行。

def receive_data():
    while True:
        data = client.recv(1024).decode()
        print(f"来自服务器的消息:{data}")

然后,我们定义发送数据的函数。这个函数将在主线程中运行。

def send_data():
    while True:
        user_input = input("请输入要发送的消息:")
        client.send(user_input.encode())

最后,我们启动线程并运行主程序。

# 创建并启动接收线程
t = Thread(target=receive_data)
t.start()

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/950b01b82fc0fefb02fba9ebc2e77c0a_24.png)

# 在主线程中运行发送函数
send_data()

服务器端实现详解

现在,让我们构建服务器端以配合客户端工作。

首先,服务器需要绑定端口并监听连接。

import socket
from threading import Thread

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/950b01b82fc0fefb02fba9ebc2e77c0a_30.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/950b01b82fc0fefb02fba9ebc2e77c0a_31.png)

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 9999))
server.listen()
print("服务器正在监听...")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/950b01b82fc0fefb02fba9ebc2e77c0a_33.png)

conn, addr = server.accept()
print(f"接收到来自 {addr} 的客户端连接")

与客户端类似,服务器也需要定义接收和发送函数。

以下是接收数据的函数:

def receive_data(connection):
    while True:
        data = connection.recv(1024).decode()
        print(f"来自客户端的消息:{data}")

以下是发送数据的函数:

def send_data(connection):
    while True:
        msg = input("服务器请输入消息:")
        connection.send(msg.encode())

最后,服务器启动线程来处理双向通信。

# 创建并启动接收线程,传入连接对象 `conn`
recv_thread = Thread(target=receive_data, args=(conn,))
recv_thread.start()

# 在主线程中运行发送函数,传入连接对象 `conn`
send_data(conn)

运行与调试

按照以下步骤运行程序:

  1. 首先运行服务器端代码。控制台将显示“服务器正在监听...”。
  2. 接着运行客户端代码。服务器端控制台会显示“接收到来自...的客户端连接”。
  3. 此时,在客户端输入消息,服务器端会收到并显示。
  4. 在服务器端输入消息,客户端也会收到并显示。

如果遇到错误,例如“receive takes at least one argument”,请检查函数定义是否正确接收了连接对象参数。添加print调试语句可以帮助定位程序执行到了哪一步。

总结

本节课中我们一起学习了如何构建一个支持全双工通信的Python网络应用。我们利用多线程技术,让receive_data()函数在后台持续运行,而send_data()函数在主线程中处理用户输入,从而实现了客户端与服务器之间的实时双向数据收发。关键在于理解每个功能模块(连接、接收、发送)都需要被正确地放入循环中,并通过线程并发执行。

016:聊天机器人服务器开发 💬

在本节课中,我们将学习如何构建一个基础的聊天机器人服务器。我们将创建一个简单的客户端-服务器模型,实现基本的消息收发功能,并运行程序验证其工作状态。


上一节我们介绍了网络编程的基础概念,本节中我们来看看如何将这些概念应用于一个具体的聊天机器人服务器。

我们可以在连接建立时添加一个调试语句。每当有客户端连接时,服务器端可以打印一条消息。

print("Connected to server.")

当服务器接收到数据时,它可以将数据发送出去。以下是核心的数据收发逻辑:

  • 服务器接收来自客户端的数据。
  • 服务器处理或转发这些数据。

对于客户端,其核心功能是获取用户输入并将其发送到服务器。

user_input = input("You: ")
client_socket.send(user_input.encode())

服务器接收到客户端发送的数据。至此,我们已经熟悉了聊天机器人客户端和服务器的基本结构。

现在我们可以运行程序,检查功能是否正常工作。

服务器绑定到端口2000,并开始监听客户端连接。一旦客户端运行,它会显示“已连接到服务器”。服务器端则会显示“从地址...获得一个客户端”。

接下来,我们需要从服务器发送数据。例如,在服务器端输入“hi”,客户端会收到“hi”。如果从客户端输入“hello”,服务器会收到“hello”。这样,双方就能进行基本的对话。

测试表明,消息传递功能运行正常。现在我们需要优化数据显示格式,使其呈现更清晰。我们可以通过标注消息来源(如“客户端说:”)来实现这一点。

一个基础的客户端与服务器聊天程序就是这样构建和运行的。这里的“聊天机器人”是一个简单的模型。在许多网站上,你会看到弹出窗口询问“需要什么帮助?”,这本质上就是你在向服务器发送数据。在连接到特定客服或向导之前,它都以聊天机器人的模式工作。

这就是关于基础聊天机器人的内容。接下来,我们将进入聊天应用程序的开发,实现多个客户端连接并相互通信的功能,这将是我们下一个应用的目标。


本节课中我们一起学习了如何构建一个简单的聊天机器人服务器。我们实现了客户端与服务器之间的连接、消息发送与接收,并验证了程序的基本功能。这为后续开发支持多用户通信的复杂聊天应用奠定了基础。

017:创建与导入客户端套接字 🚀

在本节课中,我们将要学习如何创建一个多客户端聊天应用。我们将从构建服务器和客户端的基础套接字开始,并引入多线程来处理多个客户端的并发连接。核心概念是让服务器能够接受多个客户端连接,并将一个客户端发送的消息广播给所有其他客户端。

创建聊天应用项目

首先,我们创建一个新的Python项目来构建聊天应用。我们将创建两个主要的Python文件:一个用于服务器,一个用于客户端。

以下是创建项目结构的步骤:

  1. 创建一个新的Python包或项目文件夹,命名为 chat_app
  2. 在该文件夹内,创建两个Python文件:server.pyclient.py

导入套接字模块

上一节我们介绍了项目结构,本节中我们来看看如何导入必要的模块。服务器和客户端都需要使用Python的 socket 模块来建立网络连接。

server.pyclient.py 文件的开头,我们都需要导入 socket 模块。

代码示例:导入socket

import socket

创建服务器套接字

导入模块后,我们首先在服务器端创建套接字。服务器套接字负责监听特定端口,等待客户端的连接请求。

以下是创建和配置服务器套接字的步骤:

  1. 使用 socket.socket() 创建一个新的套接字对象。
  2. 使用 bind() 方法将套接字绑定到本地主机的特定端口(例如 2000)。
  3. 使用 listen() 方法开始监听传入的连接。

代码示例:创建服务器套接字

# 在 server.py 中
server_socket = socket.socket()
server_socket.bind(('localhost', 2000))
server_socket.listen()
print("服务器已启动,正在监听端口 2000...")

创建客户端套接字

现在,让我们转向客户端。客户端套接字用于主动连接到服务器。

以下是创建和连接客户端套接字的步骤:

  1. 同样使用 socket.socket() 创建一个客户端套接字对象。
  2. 使用 connect() 方法连接到服务器的地址和端口。

代码示例:创建并连接客户端套接字

# 在 client.py 中
client_socket = socket.socket()
client_socket.connect(('localhost', 2000))
print("已连接到服务器。")

处理多客户端连接

上一节我们介绍了基本的客户端连接,但我们的目标是支持多个客户端。服务器不能只连接一个客户端,它需要持续接受新的连接。为此,我们需要一个循环来不断接受客户端。

以下是服务器端接受多客户端连接的逻辑:

  1. 在一个无限循环中,使用 accept() 方法等待并接受新的客户端连接。该方法会返回一个新的连接对象(conn)和客户端的地址(addr)。
  2. 每当有客户端连接时,打印连接信息。

代码示例:接受多客户端连接

# 在 server.py 的 listen() 之后
clients = {}  # 创建一个字典来存储所有客户端连接

while True:
    conn, addr = server_socket.accept()
    print(f"收到来自 {addr} 的连接")
    # 接下来将处理这个连接(例如,接收客户端名称并启动线程)

客户端发送名称

当客户端成功连接到服务器后,它需要做的第一件事是向服务器发送自己的名称,以便服务器识别。

以下是客户端发送名称的步骤:

  1. 提示用户输入其名称。
  2. 将名称字符串编码为字节。
  3. 通过套接字将编码后的名称发送给服务器。

代码示例:客户端发送名称

# 在 client.py 的 connect() 之后
name = input("请输入您的名称:")
client_socket.send(name.encode())

服务器接收并存储客户端信息

服务器在接受连接后,需要立即接收客户端发送过来的名称,并将该客户端的信息(名称和连接对象)存储起来,以便后续广播消息。

以下是服务器处理新客户端连接的步骤:

  1. 从新建立的连接(conn)中接收数据(客户端发送的名称)。
  2. 将接收到的字节数据解码为字符串。
  3. 将这个客户端的信息(名称和连接对象)存储在一个字典中。

代码示例:服务器接收并存储客户端

# 在 server.py 的 accept() 循环内
    data = conn.recv(1024)  # 接收最多1024字节的数据
    client_name = data.decode()
    clients[client_name] = conn  # 将客户端名称和连接对象存入字典
    print(f"客户端 {client_name} 已加入。")

为每个客户端创建线程

为了能让所有客户端同时进行通信,服务器需要为每个新连接的客户端创建一个独立的线程。这样,每个客户端的消息收发操作都不会阻塞其他客户端。

以下是创建线程的步骤:

  1. threading 模块导入 Thread 类。
  2. 定义一个函数(例如 client_task),用于处理单个客户端的消息接收和广播。
  3. 每当有新客户端连接时,就创建一个新的 Thread 对象,将 client_task 函数设为目标,并传入客户端名称、地址和连接对象作为参数。
  4. 启动线程。

代码示例:导入线程并创建客户端线程

# 在 server.py 文件开头导入
from threading import Thread

# 定义客户端处理函数
def client_task(name, addr, conn):
    # 这里将实现消息接收和广播逻辑
    pass

# 在 accept() 循环内,接收名称后
    # ... 接收 client_name ...
    thread = Thread(target=client_task, args=(client_name, addr, conn))
    thread.start()

实现消息广播逻辑

最后,我们需要在 client_task 函数中实现核心的聊天逻辑:持续接收来自该客户端的消息,并将每条消息转发(广播)给字典中存储的所有其他客户端。

以下是 client_task 函数的基本逻辑:

  1. 使用一个 while 循环持续从该客户端的连接(conn)中接收消息。
  2. 将接收到的消息解码。
  3. 遍历 clients 字典,将消息发送给除发送者本人以外的每一个客户端连接。

代码示例:客户端任务函数(广播逻辑)

def client_task(name, addr, conn):
    while True:
        try:
            data = conn.recv(1024)
            if not data:
                break  # 连接已关闭
            message = data.decode()
            # 广播给其他所有客户端
            for client_name, client_conn in clients.items():
                if client_name != name:  # 不发送给自己
                    try:
                        broadcast_msg = f"{name} 说:{message}"
                        client_conn.send(broadcast_msg.encode())
                    except:
                        pass  # 如果某个客户端连接失败,跳过
        except:
            break
    # 客户端断开连接后的清理工作
    conn.close()
    if name in clients:
        del clients[name]
    print(f"客户端 {name} 已断开连接。")

本节课中我们一起学习了如何构建一个多客户端聊天应用的基础框架。我们从创建服务器和客户端套接字开始,实现了服务器对多客户端连接的接受与处理。通过为每个客户端创建独立的线程,我们确保了聊天的并发性。核心的广播逻辑使得一个客户端发送的消息能够被所有其他客户端接收。这为构建更复杂的实时通信应用奠定了坚实的基础。

018:为客户创建消息 📨

在本节课中,我们将学习如何为聊天室中的每个客户端创建和广播消息。我们将重点讲解服务器端如何接收来自一个客户端的消息,并将其转发给其他所有连接的客户端,同时避免将消息发回给发送者本身。


上一节我们介绍了如何建立客户端连接并存储其信息。本节中我们来看看如何实现消息的接收与广播。

首先,服务器需要遍历所有已连接的客户端,以便将消息发送给它们。每个客户端的信息都存储在名为 clients 的字典中,其连接对象可以通过键(客户端名)来访问。

以下是实现消息广播的核心循环逻辑:

for client in clients:
    # 获取该客户端对应的连接列表
    connection_list = clients[client]
    # 向列表中的每个连接发送消息
    for connection in connection_list:
        connection.send(message.encode())

然而,我们发送的不能仅仅是原始数据。为了让其他客户端知道消息来自谁,我们需要构建一个完整的消息字符串,其中包含发送者的名称。

构建消息的步骤如下:

  1. 获取当前发送消息的客户端名称。
  2. 将客户端名称与接收到的消息内容组合起来,中间用冒号和空格分隔。
  3. 注意,从网络接收到的数据是字节格式,需要先解码为字符串才能进行拼接。

以下是构建消息的代码示例:

# 假设 `client_name` 是发送者名称,`raw_data` 是接收到的字节数据
decoded_message = raw_data.decode('utf-8')
full_message = f"{client_name}: {decoded_message}"
# 现在将完整消息编码回字节并发送
encoded_message = full_message.encode('utf-8')


现在,让我们将注意力转向客户端。客户端需要同时处理两件事:接收来自服务器的消息,以及发送用户输入的消息。这需要通过多线程来实现。

以下是客户端需要实现的两个核心函数:

  • receive_data 函数:在一个循环中持续从服务器套接字接收消息,解码后打印出来。
  • send_data 函数:在另一个循环中获取用户输入,编码后通过套接字发送给服务器。

为了让这两个函数能同时运行,我们使用线程。主线程用于发送消息,而新创建的线程则专门用于接收消息。

客户端启动线程的代码如下:

from threading import Thread

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/19c5c57765adcd8d4dd74ebecaa99038_59.png)

# 创建并启动接收消息的线程
receive_thread = Thread(target=receive_data)
receive_thread.start()

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/19c5c57765adcd8d4dd74ebecaa99038_61.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/19c5c57765adcd8d4dd74ebecaa99038_62.png)

# 主线程继续执行发送消息的函数
send_data()


我们完成了初步的代码,现在来运行测试一下。启动服务器和多个客户端,并尝试发送消息。

测试发现了一个问题:每个客户端都收到了自己发送的消息。这是因为我们的广播逻辑是发送给 clients 字典中的每一个客户端,没有排除消息的发送者。

为了解决这个问题,我们需要在服务器端的广播循环中添加一个条件判断:只有当遍历到的客户端名称与当前发送消息的客户端名称不同时,才向其发送消息。

修改后的广播逻辑如下:

for client in clients:
    if client != sending_client_name:  # 排除发送者自己
        connection_list = clients[client]
        for connection in connection_list:
            connection.send(message.encode())


本节课中我们一起学习了如何构建一个简单的多人聊天室的核心消息系统。我们掌握了在服务器端接收、格式化并广播消息的方法,在客户端使用多线程来同时处理消息的接收与发送,并通过条件判断解决了消息回传的问题,确保了聊天信息只广播给其他参与者。

019:学习创建聊天应用 🗨️

在本节课中,我们将学习如何利用Python的线程(Threading)和套接字(Socket)编程来构建一个基础的聊天应用程序。我们将通过一个具体的代码示例,演示多个客户端如何通过一个中央服务器进行通信。

概述

我们将创建一个典型的客户端-服务器架构的聊天应用。服务器负责接收来自所有客户端的消息,并将其广播给其他客户端。每个客户端都将运行在一个独立的线程中,以实现并发通信。本节的核心目标是理解多线程与网络套接字在构建实时通信应用中的协同工作原理。

服务器端逻辑

上一节我们介绍了网络编程的基础概念,本节中我们来看看如何实现服务器端的核心逻辑。服务器的关键职责是持续接受新的客户端连接,并为每个连接创建一个独立的线程来处理消息收发。

以下是服务器端代码的核心步骤:

  1. 创建并绑定套接字:服务器首先创建一个套接字对象,并将其绑定到特定的主机地址和端口。
  2. 监听连接:服务器开始监听来自客户端的连接请求。
  3. 接受连接并创建线程:当有客户端连接时,服务器接受该连接,获取客户端的套接字和地址信息,并为这个客户端创建一个新的线程。
  4. 处理客户端消息:在每个客户端线程中,服务器持续接收该客户端发送的消息。
  5. 广播消息:当收到一个客户端的消息后,服务器会遍历所有已连接的客户端列表,并将消息发送给除消息来源以外的所有其他客户端。

其核心广播逻辑可以用以下伪代码表示:

for client in connected_clients:
    if client != message_sender:
        client.send(message)

客户端连接与测试

理解了服务器如何工作后,我们来看看客户端的连接过程以及如何进行测试。客户端需要连接到服务器,并能够同时发送和接收消息。

以下是启动和测试的步骤:

  1. 首先运行服务器端程序。
  2. 然后启动多个客户端程序,每个客户端在连接时都需要输入一个唯一的名称(如ABC, XYZ)。
  3. 当客户端ABC发送消息“hi guys”时,服务器会收到这条消息。
  4. 根据广播逻辑,服务器会将此消息转发给所有其他客户端(如XYZ),但不会发回给ABC本身。
  5. 同样,其他客户端发送的消息也会被广播给除了发送者之外的全体成员。

通过这种方式,三个客户端(例如ABC, PQR, XYZ)就能通过服务器进行群组聊天。每个客户端发送的消息都会被其他客户端接收到,从而实现基本的聊天功能。

应用原理与扩展

我们已经成功创建了一个基础的聊天应用。这个示例清晰地展示了如何使用线程管理多个并发的网络连接,以及如何使用套接字进行数据传输。

本节课中我们一起学习了构建聊天应用的核心模式:一个中央服务器多个客户端线程。这种客户端-服务器模型和广播通信机制是许多实时应用(如在线聊天室、简单协作工具)的基础。

掌握这些原理至关重要,因为它们是更高级概念的基础。例如,当你希望创建一个具有图形界面的应用,比如一个包含录音和发送按钮的语音聊天工具时,你可以在前端界面之下,利用我们今天所学的这种套接字与线程的通信架构来处理网络数据传输部分。

020:PyCharm数据库与SQLite 🗄️

在本节课中,我们将学习数据库的基础知识,特别是SQLite数据库,并探讨如何在Python应用程序中使用它来存储和管理数据。我们将从理解数据库的必要性开始,然后学习如何创建数据库和表,最后将其与我们的聊天应用程序联系起来。

上一节我们介绍了网络编程和多线程,并构建了一个基础的聊天应用。本节中,我们来看看如何为这个应用引入数据库来存储更复杂的用户数据。

为什么需要数据库?

在之前的聊天应用中,我们使用字典来存储客户端信息,例如用户名和连接对象。然而,这种方法存在局限性。

以下是字典存储的局限性:

  • 字典只能存储有限的信息,通常是一对键值(如 name: connection)。
  • 无法高效地存储和查询更复杂的用户信息,例如地址、电话号码或电子邮件。
  • 当数据量增大(例如有上千个客户端)或需要长期保存时,字典并非理想的解决方案。

虽然可以将数据存储在文件中(如文本文件、CSV或Excel),但使用文件进行复杂的数据操作、查询和调试通常比较繁琐。相比之下,数据库提供了更结构化、高效和可靠的数据管理方式。大多数现代应用程序都广泛使用数据库。

引入SQLite数据库

为了学习数据库与Python的集成,我们将从SQLite开始。SQLite是一个轻量级的数据库引擎,非常适合入门和开发原型。

我们的长期目标是将聊天应用扩展,把客户端的详细信息(如邮箱、生日等)存储在数据库中。但首先,让我们学习SQLite的基础操作。

创建SQLite数据库和表

我们将通过命令行来创建数据库和表。以下是具体步骤:

  1. 打开Anaconda Prompt(或系统终端)。
  2. 使用 sqlite3 命令创建并进入一个名为 company.db 的数据库文件。命令是:sqlite3 company.db
  3. 在数据库环境中,我们可以创建表。例如,创建一个名为 employee 的表。创建表的SQL命令通常如下格式:
    CREATE TABLE employee (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT
    );
    

本节课中我们一起学习了数据库的基本概念及其在应用中的重要性,并初步实践了如何使用命令行创建SQLite数据库和表。在接下来的课程中,我们将深入学习如何在Python代码中连接和操作这个数据库,最终实现为聊天应用添加数据持久化功能。

021:创建表格的命令 📝

在本节课中,我们将学习如何在SQLite数据库中创建表格。这是构建数据库结构的第一步,我们将详细讲解创建表格的命令语法、数据类型定义以及一些关键约束。

上一节我们介绍了SQLite的基本概念和命令行工具,本节中我们来看看如何使用SQL命令来定义和创建数据表。

创建表格的基础语法

创建表格的核心命令是 CREATE TABLE。其基本语法结构如下:

CREATE TABLE table_name (
    column1 datatype constraint,
    column2 datatype constraint,
    ...
);
  • CREATE TABLE 是SQL关键字,用于指示创建新表。
  • table_name 是你为新表起的名字。
  • 括号 () 内定义了表的列(字段)。
  • 每一列的定义包括列名、数据类型和可选的约束(如 NOT NULL, PRIMARY KEY 等)。
  • 每个SQL语句(除了部分点命令)必须以分号 ; 结尾。

创建表格的实践步骤

以下是创建一个名为 employee 的员工表的详细步骤。

首先,我们尝试一个不完整的命令,以理解错误信息:

CREATE TABLE employee;

执行此命令会报错,因为创建表时必须至少定义一列。

接下来,我们创建一个包含基本列定义的表:

CREATE TABLE employee (
    id INTEGER,
    first_name TEXT,
    last_name TEXT,
    salary INTEGER,
    email TEXT,
    dob DATE
);

执行成功后,可以使用 .tables 命令查看数据库中现有的表,确认 employee 表已创建。

为表格添加约束

约束用于定义关于列中数据的规则,确保数据的准确性和可靠性。以下是几个重要的约束:

  • PRIMARY KEY:唯一标识表中的每一行。一个表只能有一个主键。
  • AUTOINCREMENT:通常与 INTEGER PRIMARY KEY 一起使用,确保主键值自动递增。
  • NOT NULL:强制该列不能存储 NULL 值(即必须有值)。
  • 数据类型:定义列可以存储的数据种类,如 INTEGER(整数)、TEXT(文本)、REAL(浮点数)、DATE(日期)等。

让我们重新创建一个带有约束的、更完善的 employee 表。首先,删除旧表:

DROP TABLE employee;

然后,创建新表:

CREATE TABLE employee (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    first_name TEXT NOT NULL,
    last_name TEXT,
    salary INTEGER,
    email TEXT,
    dob DATE NOT NULL
);

在这个例子中:

  • id 是自动递增的主键。
  • first_namedob 是必填项(NOT NULL)。
  • last_namesalaryemail 允许为空。

创建后,可以使用 .schema employee 命令查看该表的完整结构定义。

查看表格结构与数据

创建表格后,我们可以使用以下命令进行查看:

  1. 查看所有表格:使用 .tables 命令。
  2. 查看表格结构:使用 .schema table_name 命令。
  3. 查看表格数据:使用 SELECT 语句。为了更清晰地显示列标题,可以先开启标题显示模式:
    .header on
    SELECT * FROM employee;
    
    由于我们尚未插入数据,此查询将返回一个空的结果集,但会显示列名。

本节课中我们一起学习了在SQLite中创建表格的核心命令 CREATE TABLE。我们掌握了定义列名、数据类型(如 INTEGER, TEXT, DATE)以及添加约束(如 PRIMARY KEY, AUTOINCREMENT, NOT NULL)的方法。此外,我们还学习了如何使用 .tables.schemaDROP TABLE 等命令来管理和查看数据库中的表结构。这是构建任何数据库应用的基础,为后续的数据插入、查询和操作做好了准备。

022:在表中插入数值

在本节课中,我们将学习如何在SQLite数据库的表中插入数据。我们将介绍基本的INSERT语句,包括插入所有列数据和选择性插入部分列数据,并演示如何查询和查看已插入的数据。

概述

上一节我们介绍了如何创建数据库和表。本节中,我们来看看如何向已创建的表中填充数据。我们将使用INSERT INTO语句,并学习其两种主要用法:为所有列插入值,以及仅为指定的列插入值。

向表中插入数据

要向表中插入数据,我们可以使用INSERT命令。

基本的语法格式是:

INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...);

插入所有列的值

如果你想为表中的每一列都提供值,可以省略列名,直接按表定义的列顺序提供所有值。

以下是插入所有列值的示例:

INSERT INTO employee VALUES (1, ‘XYZ’, ‘Doe’, 50000, ‘xyz@company.com’, ‘1984-05-18’);

选择性插入部分列的值

有时,你可能只想为表中的部分列插入数据,其他列允许为空或使用默认值。这时,你需要在INSERT INTO语句中明确指定要插入数据的列名。

以下是选择性插入的示例。假设我们只想插入F_name(名)、email(邮箱)和DOB(出生日期)这三列:

INSERT INTO employee (F_name, email, DOB) VALUES (‘XYZ’, ‘xyz@company.com’, ‘1984-05-18’);

在这个例子中:

  • F_name的值是字符串‘XYZ’
  • email的值是字符串‘xyz@company.com’
  • DOB是日期类型,其值格式为‘YYYY-MM-DD’,即‘1984-05-18’

插入多条记录

你可以连续执行多个INSERT语句来添加多条员工记录。

以下是插入更多员工的示例:

-- 插入员工PQR,提供更多信息
INSERT INTO employee (F_name, L_name, email, DOB) VALUES (‘PQR’, ‘PQSD’, ‘pqr@company.com’, ‘1994-01-01’);

-- 插入另一个名为XYZ但信息不同的员工
INSERT INTO employee (F_name, email, DOB) VALUES (‘Xyz’, ‘xyz@company.com’, ‘1974-08-05’);

查询与验证数据

插入数据后,我们需要验证操作是否成功。最常用的方法是使用SELECT语句查询表中的所有数据。

要查看employee表中的所有行和所有列,请使用以下命令:

SELECT * FROM employee;

执行此命令后,你将看到一个表格形式的输出,显示所有列(如IDF_nameL_namesalaryemailDOB)以及我们刚刚插入的每一行数据。即使某些列(如未指定的L_namesalary)值为空(NULL),它们也会显示在结果中。

使用WHERE子句进行筛选查询

在实际应用中,我们经常不需要查看所有数据,而是希望根据特定条件进行筛选。这时可以使用WHERE子句。

例如,如果我们只想查看出生日期在某个特定年份之后的员工,可以这样查询:

SELECT * FROM employee WHERE DOB > ‘1990-01-01’;

或者,如果我们只想查看名字为‘XYZ’的员工:

SELECT * FROM employee WHERE F_name = ‘XYZ’;

WHERE子句是SQL中用于过滤结果集的核心工具,它允许我们执行更精确的数据检索。

退出SQLite

完成所有数据库操作后,你可以使用以下命令退出SQLite命令行界面:

.exit

执行此命令后,你将返回到操作系统的常规命令行环境。

总结与过渡

本节课中,我们一起学习了SQLite中插入数据的基本操作。我们掌握了使用INSERT INTO语句插入完整或部分数据,使用SELECT *查询全部数据,并初步了解了WHERE子句的筛选功能。这些是操作任何SQL数据库的基础。

现在,我们已经学会了如何手动在数据库中创建和填充数据。然而,在数据分析工作中,我们更希望通过Python程序来自动化这些过程。下一节,我们的目标将是建立Python代码与SQLite数据库之间的连接。我们将学习如何从Python中创建数据库连接、执行SQL命令(如插入和查询),从而实现用Python程序来管理和操作数据,为更复杂的数据分析任务打下基础。

023:连接数据库与Python代码 🗄️➡️🐍

在本节课中,我们将学习如何使用Python代码连接并操作SQLite数据库。我们将创建一个数据库,在其中建立数据表,并向表中插入数据。

概述

我们将通过Python的sqlite3库,逐步演示如何连接到SQLite数据库、创建数据表、插入数据,并最终提交更改。整个过程将展示Python与数据库交互的基本流程。

创建连接与游标

首先,我们需要导入sqlite3库并建立与数据库的连接。如果指定的数据库不存在,系统会自动创建一个新的数据库文件。

import sqlite3

接下来,我们使用connect函数连接到名为school.db的数据库。连接对象将帮助我们获取一个游标。

connection = sqlite3.connect('school.db')
cursor = connection.cursor()

游标是数据库操作的核心工具,它允许我们指向数据库中的特定位置,并执行SQL查询命令。

创建数据表

在建立连接后,我们可以开始定义数据库的结构。以下是创建students(学生)表和teachers(教师)表的步骤。

我们将编写SQL查询语句来创建表。每个SQL命令被称为一个查询。

query_student_table = """
CREATE TABLE student (
    roll_number INTEGER,
    name VARCHAR(20),
    gender VARCHAR(1),
    dob DATE
);
"""

query_teacher_table = """
CREATE TABLE teacher (
    id INTEGER,
    name VARCHAR(20),
    gender VARCHAR(1),
    dob DATE
);
"""

编写完查询语句后,我们需要使用游标的execute方法来执行它们,从而在数据库中创建相应的表。

cursor.execute(query_student_table)
cursor.execute(query_teacher_table)

向表中插入数据

表结构创建完成后,下一步是向表中添加数据。我们将分别为students表和teachers表插入多条记录。

以下是向students表插入数据的示例。我们使用INSERT INTO语句,并注意字符串值需要用双引号括起来。

insert_student_query = """
INSERT INTO student (roll_number, name, gender)
VALUES (12, "XYZ", "M");
"""
cursor.execute(insert_student_query)

我们可以复制并修改上述代码,为students表插入更多记录,例如学号为45和54的学生。

cursor.execute("""INSERT INTO student VALUES (45, "PQR", "F", NULL);""")
cursor.execute("""INSERT INTO student VALUES (54, "ABC", "F", NULL);""")

类似地,我们为teachers表插入数据。每条记录包含ID、姓名和性别。

cursor.execute("""INSERT INTO teacher (id, name, gender) VALUES (12, "UVW", "M");""")
cursor.execute("""INSERT INTO teacher (id, name, gender) VALUES (13, "XYZ", "F");""")
cursor.execute("""INSERT INTO teacher (id, name, gender) VALUES (14, "DEF", "M");""")

提交更改与关闭连接

所有数据操作完成后,必须提交更改,否则数据库不会保存任何修改。提交后,关闭数据库连接是一个好习惯。

connection.commit()
connection.close()

运行整个脚本后,数据库文件school.db将被创建并保存在项目目录中。你可以使用数据库浏览器工具查看其中的studentteacher表,确认数据已成功插入。

总结

本节课中我们一起学习了使用Python连接和操作SQLite数据库的核心步骤。我们掌握了如何建立连接、获取游标、执行SQL查询来创建表、插入数据,并最终提交和关闭连接。这是进行数据持久化存储的基础。

024:数据库查询 📊

在本节课中,我们将学习如何使用Python从SQLite数据库中读取数据。我们将涵盖建立数据库连接、执行查询以及处理查询结果的基本步骤。

上一节我们介绍了如何创建数据库和表,本节中我们来看看如何从这些表中读取数据。

建立连接与游标

首先,我们需要导入sqlite3模块,并与数据库文件建立连接。连接成功后,创建一个游标对象来执行SQL命令。

import sqlite3

# 连接到数据库文件
connection = sqlite3.connect('school.db')
# 创建游标
cursor = connection.cursor()

执行查询与获取结果

以下是执行SQL查询并从数据库中获取数据的基本步骤。

  1. 定义查询语句:编写你想要执行的SQL查询。
  2. 执行查询:使用游标的.execute()方法运行SQL语句。
  3. 获取结果:使用.fetchall()方法获取所有查询结果。
  4. 处理结果:遍历结果集并打印或处理每一行数据。
  5. 关闭连接:操作完成后,务必关闭数据库连接以释放资源。
# 1. 定义查询:获取所有学生
query = "SELECT * FROM students"
# 2. 执行查询
cursor.execute(query)
# 3. 获取所有结果
result = cursor.fetchall()

# 4. 处理结果
print("学生详情:")
for student in result:
    print(student)

# 5. 关闭连接
connection.close()

使用WHERE子句进行筛选

我们可以使用WHERE子句来筛选特定的数据。例如,只想获取性别为男性的学生记录。

# 查询所有男性学生
query_male = "SELECT * FROM students WHERE gender = 'M'"
cursor.execute(query_male)
male_students = cursor.fetchall()

print("男性学生:")
for student in male_students:
    print(student)

同样地,我们可以查询所有女性教师。

# 查询所有女性教师
query_female_teachers = "SELECT * FROM teachers WHERE gender = 'female'"
cursor.execute(query_female_teachers)
female_teachers = cursor.fetchall()

print("女性教师:")
for teacher in female_teachers:
    print(teacher)

总结

本节课中我们一起学习了从SQLite数据库读取数据的关键操作。我们掌握了如何建立连接、创建游标、执行SELECT查询,以及如何使用WHERE子句过滤结果。这些是进行数据检索和后续分析的基础。在接下来的课程中,我们将把这些知识应用到实际项目中,例如在聊天应用程序中存储和访问用户数据。

025:聊天应用中的变更实施 💬

在本节课中,我们将学习如何修改一个简单的聊天应用,使其能够收集并处理客户端发送的更多信息,例如姓名和电子邮件地址。我们将重点讲解如何在客户端收集数据,在服务器端解析数据,并实现基本的数据库连接概念。


上一节我们介绍了聊天应用的基本结构,本节中我们来看看如何修改客户端和服务器端代码,以接收和处理更多用户信息。

现在,为了在聊天应用中进行更改,我们可以直接转到聊天应用。让我们点击客户端和服务器。我们只想做一些修改,主要是希望在这里实现数据库连接功能。

我们希望当客户端连接时,不仅获取其姓名,还能获取他的许多其他详细信息。例如,除了姓名,他可能还会输入其他信息。假设他输入姓名并进入后,你可以询问他来自哪里。也许你可以询问他的电子邮件。所以,需要输入“请输入您的电子邮件”。这是一件事。

假设除了电子邮件,我还想让他提供出生日期。让我说输入。好吧,目前先不考虑出生日期,我们可以暂时不处理它。但当你除了姓名和电子邮件之外还有几项信息时,比如他的IP地址也需要存储。类似这样的信息需要存储。

我们可以这样做。我们可以直接创建一个字典。假设创建一个 client_info 字典,它将是一个空字典。但当姓名被输入时,我可以说 client_info。我将把 name 作为其中一个键值对放进去。client_info['name'] 等于输入“请输入您的姓名”,而 client_info['email'] 将成为 client_info 的另一个键。这样,我们就可以从用户那里获取这两项信息。当我发送时,我将对它们进行编码,基本上同时获取 client_info['name']client_info['email'],将它们编码后一起发送出去。

也许我们可以创建一个字符串。假设 client_final_info 字符串将包含他的姓名。那么,我们就不在这里创建字典了,因为无论如何我们都要将其附加到其他地方。所以,我们在这里只使用两个简单的变量,一个是 name,另一个是 email。我将在这里定义它们,然后创建一个类似 client_info_now 的变量,它将等于 name 和电子邮件。我将以某种方式存储它,例如“姓名: [姓名]”,然后是“电子邮件: [电子邮件]”。现在,当我发送到服务器时,我将只发送 client_info_now.encode()。这样,我就发送了用冒号分隔的姓名和电子邮件。在姓名之后,我可以加一个逗号,以便两者之间有清晰的区分。这样,一个是姓名,一个是电子邮件,两者都在那里。

最初发送时,当然,我的服务器端应该相应地进行更改。好的,编辑器在哪里?它说“姓名: [你的姓名]”和“电子邮件: [输入]”。好的,让我检查一下。我认为这里有一个错误。好了,保存了。让我转到服务器端。

在服务器端,我基本上会收到来自客户端的连接 conn 和地址 addr。我手头有客户端的地址和姓名。所以,当我执行 conn.recv() 接收数据时,收到的将不仅仅是姓名,而是客户端发送的完整信息 client_final_info。我需要存储它们以备后用。

假设 client_final_info。使用 .split 方法在逗号处分割,这将给我一个列表 client_info_list。现在,这个特定的列表,我们必须处理它。分割后,我们将得到一个列表。假设我们心中有这个概念。然后我需要将它们再次分割成不同的部分。我有“姓名: [姓名]”和地址等信息。我可以直接在这里打印:“一个名为 [姓名] 的客户端已连接。”而不是在这里打印。好的,所以我复制这个并放在这里。我删除这个。我们收到了来自地址 addr 和姓名为 client_final_info 的客户端的连接。

我必须再次分割它,因为 client_info_list 包含这两部分。所以,假设索引0包含“姓名: [姓名]”,所以我需要再次在冒号处分割它。当有冒号时,我将在冒号处分割。也许我们可以尝试一下,只需在 Jupyter notebook 上快速测试一下。


以下是关键步骤的代码示例:

客户端修改示例:

# 收集用户信息
name = input("请输入您的姓名: ")
email = input("请输入您的电子邮件: ")

# 构建发送字符串,格式为 "姓名:xxx,电子邮件:yyy"
client_info_now = f"姓名:{name},电子邮件:{email}"

# 发送编码后的信息
client_socket.send(client_info_now.encode())

服务器端修改示例:

# 接收客户端信息
data = conn.recv(1024).decode()
if data:
    # 首先在逗号处分割,得到 ["姓名:xxx", "电子邮件:yyy"]
    info_parts = data.split(',')
    
    # 初始化字典存储解析后的信息
    client_data = {}
    for part in info_parts:
        # 每个部分在冒号处分割
        key, value = part.split(':')
        client_data[key] = value
    
    # 提取姓名
    client_name = client_data.get('姓名', '未知用户')
    print(f"一个名为 {client_name} 的客户端已连接。地址: {addr}")

让我们在 Jupyter notebook 中快速测试一下字符串分割的逻辑。假设这是你将收到的字符串:"姓名:XYZ,电子邮件:XYZ@company.com"

我们将检查如何分割。首先,在逗号处分割得到列表:["姓名:XYZ", "电子邮件:XYZ@company.com"]。然后,遍历列表中的每个元素,在冒号处再次分割,以提取键值对。


本节课中我们一起学习了如何扩展聊天应用的功能,使其能够从客户端接收并解析包含多个字段(如姓名和电子邮件)的复合信息。我们通过构建特定格式的字符串在客户端组织数据,并在服务器端使用 .split() 方法进行两次分割来提取所需信息。这为将来集成数据库存储更丰富的用户资料奠定了基础。

026:解码客户信息与数据库存储 🧩

在本节课中,我们将学习如何从接收到的二进制数据中解码客户信息,并将这些信息(如姓名和邮箱)存储到SQLite数据库中。整个过程涉及字符串分割、数据解码和数据库操作。

上一节我们介绍了网络通信中数据的接收,本节中我们来看看如何处理接收到的具体数据。

解码与分割客户信息

首先,我们接收到的是一个二进制格式的客户信息。为了处理它,我们需要将其解码为字符串,然后根据特定分隔符进行分割。

以下是处理步骤:

  1. 对二进制数据 client_score_info 调用 .decode() 方法,将其转换为字符串。
  2. 使用 .split() 方法,在逗号 , 处分割字符串,得到一个包含姓名和邮箱的列表。
client_info_str = client_score_info.decode()
client_info_list = client_info_str.split(',')

执行上述代码后,client_info_list 将是一个包含两个元素的列表,例如 ['XYZ', 'xyz@company.com']

提取姓名与邮箱

从分割得到的列表中,我们可以轻松地提取出独立的姓名和邮箱变量。

以下是提取方法:

  • 姓名 是列表的第一个元素(索引0)。
  • 邮箱 是列表的第二个元素(索引1)。

name = client_info_list[0]
email = client_info_list[1]

现在,name 变量存储了客户姓名(如“XYZ”),email 变量存储了客户邮箱(如“xyz@company.com”)。这些数据已经准备好被存入数据库。

连接与创建数据库

为了存储数据,我们需要先建立与SQLite数据库的连接,并确保存在一张可以存储客户信息的表。

以下是数据库初始化步骤:

  1. 导入 sqlite3 模块。
  2. 使用 sqlite3.connect() 连接到数据库文件(例如 chat.db)。
  3. 通过连接创建游标(cursor)。
  4. 使用游标执行SQL语句,创建 clients 表。该表包含 client_nameemailaddr 等字段。

import sqlite3

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3c168c1116475ebd86ae574fc315e33_18.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3c168c1116475ebd86ae574fc315e33_20.png)

# 连接到数据库(如果不存在则创建)
connection = sqlite3.connect('chat.db')
cursor = connection.cursor()

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3c168c1116475ebd86ae574fc315e33_22.png)

# 创建clients表
cursor.execute('''
    CREATE TABLE IF NOT EXISTS clients (
        client_name VARCHAR(20),
        email VARCHAR(20),
        addr VARCHAR(20)
    )
''')
connection.commit()

插入数据到数据库

在成功解码信息并建立数据库连接后,最后一步是将提取出的客户数据插入到数据库表中。

以下是数据插入方法:
在每次需要保存数据时,使用游标执行 INSERT SQL语句,将 nameemail 变量的值作为参数传入。

# 将解码后的客户信息插入数据库
cursor.execute("INSERT INTO clients (client_name, email) VALUES (?, ?)", (name, email))
connection.commit()

请注意,在实际的网络服务器应用中,每个客户端连接通常在一个独立的线程中处理。因此,为每个线程创建独立的数据库游标是必要的,以避免数据操作冲突。

本节课中我们一起学习了如何解码二进制格式的客户信息,通过字符串分割提取关键数据,并最终将这些数据持久化存储到SQLite数据库中。这是构建数据处理管道的关键一步。

027:客户端元素索引与数据库插入 🧩

在本节课中,我们将学习如何将从客户端接收到的数据(如姓名和邮箱)进行解码、索引,并最终通过SQL查询插入到数据库的clients表中。我们将重点关注数据提取、SQL语句构建以及错误调试的过程。


上一节我们介绍了如何建立服务器与客户端的连接并接收数据。本节中,我们来看看如何处理接收到的数据,并将其存入数据库。

我们已经解码了客户端信息,并将其放入一个列表中。在这个列表中,我们索引了第0个元素(姓名)和第一个元素(邮箱)。现在,我们已获得来自客户端的连接,需要执行下一步操作。

我们需要将姓名和邮箱存储到名为clients的特定表中。为此,我们需要编写SQL查询语句。

以下是构建插入查询的步骤:

  1. 编写基础SQL语句:使用INSERT INTO语句指定目标表(clients)和要插入的列(例如name, email, addr)。
  2. 处理变量插入:查询中的值需要是变量(如name, email, addr),而非固定的字符串。我们使用字符串的.format()方法来实现。
  3. 执行与提交:使用数据库游标的execute()方法运行SQL查询,然后通过连接的commit()方法提交事务,确保数据永久保存。

核心的SQL插入语句和Python执行代码如下:

query = """INSERT INTO clients (name, email, addr) VALUES ("{}", "{}", "{}")""".format(name, email, addr)
cursor.execute(query)
connection.commit()


在编写并执行代码后,我们遇到了错误,需要进行调试。

首先遇到的错误是“near ... syntax error”。这表明SQL语句的语法可能有问题。经过检查,问题出在addr变量上。addr本身是一个包含IP和端口的元组(例如('127.0.0.1', 65432)),直接放入SQL语句会导致语法错误。

为了诊断问题,我们打印了addr及其类型:

print(addr)
print(type(addr))

输出确认addr是一个元组。我们需要将其转换为字符串才能插入数据库。可以使用str()函数进行转换:

addr_str = str(addr)

解决了addr的问题后,再次运行程序,又遇到了新的错误:“‘str’ object has no attribute ‘decode’”。这个错误发生在尝试对已经是字符串的name变量再次调用.decode()方法时。回顾代码,我们在更早的阶段已经对接收到的字节流进行了解码,因此此处的name已经是字符串,无需再次解码。我们移除了这行多余的代码。


本节课中我们一起学习了如何索引客户端数据、构建动态SQL插入查询,并将数据存入数据库。我们遇到了两个典型的错误:数据类型不匹配(元组无法直接插入)和多余的方法调用(对字符串进行解码)。通过打印变量内容和类型,我们有效地定位并解决了这些问题。这个过程展示了数据处理和错误调试的基本工作流。

028:处理来自客户端地址的连接

在本节课中,我们将学习如何从客户端连接中提取地址信息,并将其正确地存储到数据库中。我们将重点关注如何解析客户端地址、构建正确的SQL查询语句,以及如何验证数据库操作是否成功。

上一节我们介绍了基本的客户端-服务器通信模型,本节中我们来看看如何将客户端的连接信息(如IP地址)记录到数据库中。

处理客户端地址

当客户端连接到服务器时,服务器会获取一个包含客户端地址的元组。我们需要从这个元组中提取出字符串格式的地址,以便将其插入到数据库中。

以下是提取地址的步骤:

  1. 客户端连接时,服务器会获得一个地址元组,例如 (‘127.0.0.1‘, 65432)
  2. 这个元组的第一个元素(索引0)就是客户端的IP地址字符串。
  3. 我们可以直接使用这个字符串,而不是整个元组,来构建SQL插入语句。

核心操作代码如下:

# addr 是包含客户端地址的元组,如 (‘127.0.0.1‘, 65432)
client_address = addr[0]  # 获取IP地址字符串

修正SQL查询语句

在构建SQL查询时,必须确保语法正确。一个常见的错误是遗漏了语句结束的分号或括号不匹配。

以下是构建正确INSERT语句的要点:

  1. SQL语句中的字符串值必须用单引号括起来。
  2. INSERT INTO 语句的 VALUES 部分必须用括号包围。
  3. 语句末尾通常需要分号。

修正后的代码示例如下:

# 正确的INSERT语句格式
sql_query = “INSERT INTO clients (name, email, address) VALUES (‘“ + name + “‘, ‘“ + email + “‘, ‘“ + client_address + “‘);”

验证数据库操作

在执行数据库插入操作后,验证数据是否成功写入是一个好习惯。我们可以通过查询数据库来确认。

以下是验证数据的两种方法:

  1. 在服务器启动时查询:服务器启动后,立即查询并打印数据库中已注册的所有客户端,以了解初始状态。
  2. 在每次有新客户端连接后查询:每当有新客户端成功注册后,再次查询并打印完整的客户端列表,以确认新数据已加入。

核心查询代码如下:

# 查询clients表中的所有记录
cursor.execute(“SELECT * FROM clients;“)
result = cursor.fetchall()  # 获取所有结果
print(“当前注册的客户端:“, result)

完整流程演示

让我们通过一个例子来串联整个流程。假设服务器正在运行,一个名为“PQR”、邮箱为“PQR@gmail.com”的客户端尝试连接。

流程步骤如下:

  1. 客户端连接到服务器,并发送其名称和邮箱。
  2. 服务器接收连接,从 addr 元组中提取出客户端IP地址(例如 ‘127.0.0.1‘)。
  3. 服务器构建并执行SQL语句:INSERT INTO clients (name, email, address) VALUES (‘PQR‘, ‘PQR@gmail.com‘, ‘127.0.0.1‘);
  4. 插入操作完成后,服务器执行 SELECT * FROM clients; 查询。
  5. 查询结果被打印出来,显示数据库中所有客户端的记录,其中包含了新加入的“PQR”。

通过这种方式,我们不仅存储了客户端信息,还能实时查看数据库的状态。

本节课中我们一起学习了如何从客户端连接中提取地址信息、构建正确的SQL插入语句来将信息存入数据库,以及如何通过查询来验证操作结果。掌握这些步骤对于构建能够持久化记录客户端信息的网络应用至关重要。

029:添加电子邮件与客户端名称 📧

在本节课中,我们将学习如何扩展我们的聊天服务器功能,为客户端消息添加电子邮件地址和客户端名称。我们将通过查询数据库来获取客户端的详细信息,并将其整合到消息广播中。

上一节我们介绍了基本的客户端-服务器通信模型,本节中我们来看看如何从数据库中提取客户端信息并丰富消息内容。

概述与准备

为了添加电子邮件信息,我们需要在服务器端修改消息处理逻辑。当服务器收到一条消息时,它不仅需要广播消息内容,还需要附上发送者的名称和电子邮件地址。这要求我们在发送消息前,先查询数据库以获取该客户端的详细信息。

首先,我们需要确保数据库表结构正确,并包含电子邮件字段。我们将重新创建 clients 表。

以下是创建和初始化数据库的步骤:

  1. 删除已存在的旧表。
  2. 创建一个包含 nameemail 字段的新表。
  3. 插入一些示例客户端数据。

# 假设 conn 是数据库连接,cursor 是游标
cursor.execute("DROP TABLE IF EXISTS clients")
cursor.execute("CREATE TABLE clients (name TEXT, email TEXT)")
# 插入示例数据
clients_data = [('XYZ', 'xyz@gmail.com'), ('PQR', 'pqr@company.co'), ('DEF', 'def@company.com')]
cursor.executemany("INSERT INTO clients VALUES (?, ?)", clients_data)
conn.commit()

修改服务器消息处理逻辑

服务器在 handle_client 函数中接收来自客户端的消息。我们需要在广播消息之前,先根据客户端名称查询其电子邮件地址。

核心修改在于,当服务器收到消息后,执行一个SQL查询来获取该客户端的完整信息。

def handle_client(client_socket, client_address, client_name):
    # ... 连接建立代码 ...
    while True:
        try:
            message = client_socket.recv(1024).decode('utf-8')
            if not message:
                break

            # 关键步骤:查询数据库获取客户端邮箱
            cursor.execute("SELECT * FROM clients WHERE name = ?", (client_name,))
            result = cursor.fetchone()  # 获取查询结果的第一行

            # 构建要广播的消息格式:`客户端名 (邮箱): 消息内容`
            broadcast_message = f"{client_name} ({result[1]}): {message}"

            # 将消息广播给所有其他客户端
            for client in clients:
                if client != client_socket:
                    try:
                        client.send(broadcast_message.encode('utf-8'))
                    except:
                        # 处理发送失败的情况
                        pass
        except:
            break
    # ... 连接关闭和清理代码 ...

注意:在多线程环境中,每个线程必须使用自己的数据库连接和游标对象。不能在线程间共享SQLite对象。因此,在 handle_client 函数内部,需要创建新的数据库连接。

处理多线程数据库连接

由于每个客户端连接都在独立的线程中处理,我们必须确保每个线程都有自己的数据库连接,以避免 SQLite objects created in a thread can only be used in that same thread 错误。

以下是修改后的线程处理函数结构:

def handle_client(client_socket, client_address, client_name):
    # 为当前线程创建独立的数据库连接
    thread_conn = sqlite3.connect('chat.db', check_same_thread=False)
    thread_cursor = thread_conn.cursor()

    # ... 消息接收和广播逻辑(使用 thread_cursor 进行查询)...

    # 客户端断开连接后,关闭线程内的数据库连接
    thread_cursor.close()
    thread_conn.close()

运行与测试流程

完成代码修改后,我们可以启动服务器和多个客户端进行测试。

以下是启动和注册客户端的步骤:

  1. 启动服务器。
  2. 启动客户端1,输入名称 XYZ 和邮箱 xyz@gmail.com
  3. 启动客户端2,输入名称 PQR 和邮箱 pqr@company.co
  4. 启动客户端3,输入名称 DEF 和邮箱 def@company.com

测试时,从任一客户端(如XYZ)发送消息“hi”。其他所有客户端(PQR和DEF)都应收到格式为 XYZ (xyz@gmail.com): hi 的消息。同样,从PQR发送的消息会以 PQR (pqr@company.co): [消息] 的格式广播给XYZ和DEF。

总结

本节课中我们一起学习了如何为聊天应用的消息添加客户端名称和电子邮件信息。我们通过以下步骤实现了这个功能:

  1. 重构数据库:确保 clients 表包含必要的字段。
  2. 修改服务器逻辑:在广播消息前,先根据客户端名查询数据库,获取其邮箱地址。
  3. 处理多线程:为每个客户端处理线程创建独立的数据库连接,避免SQLite的线程限制问题。
  4. 格式化消息:将查询到的信息整合成 名称 (邮箱): 消息 的格式进行广播。

通过这些修改,我们构建了一个功能更丰富的群聊系统,每条消息都清晰地显示了发送者及其联系信息。在接下来的课程中,我们将在此基础上继续开发更多高级功能。

030:分析数据集

在本节课中,我们将要学习数据分析在物联网(IoT)领域的重要性,并介绍用于处理和分析大规模数据集的核心Python库。我们将从NumPy库开始,了解其基本概念和功能。

物联网的一个重要方面是,我们可能需要分析从不同传感器持续获取的大量数据。这些数据量非常庞大。在初始阶段可能不会用到,但在后续阶段,当需要分析大量数据、进行数值计算或处理真正庞大的数据集时,这些技能就至关重要。

例如,假设有一家像惠而浦这样的公司,销售洗衣机和空调。考虑到一个国家可能有约50%的人口使用洗衣机,市场上的洗衣机数量可能达到数十万甚至数百万台。如果一家公司决定在洗衣机中安装物联网设备来监控不同传感器的数据,以分析客户使用习惯等,那么就需要处理和分析海量数据。在这种情况下,服务器端会涌入大量数据,因此必须借助数据分析库。

我们将创建一个专门用于物联网数据分析的包。为此,我们需要了解Python中用于数据分析的库。让我们从第一个库开始。

使用NumPy库

我们将使用的第一个库是NumPy。NumPy是数据分析的基础库,它提供了高效处理数值计算的功能。

在深入NumPy之前,让我们再次明确数据分析的定义。数据分析是一门管理、分析大型数据集或大数据的科学。它需要工具来转换和建模数据。多年来,Python通过提供NumPy、SciPy、Pandas和Matplotlib等库,已经发展成为处理不同组件和进入数据分析领域的一个非常有效的解决方案。Matplotlib尤其用于在图表上绘制数据。

什么是NumPy?

NumPy是“Numeric Python”(数值Python)的缩写。它是一个开源的Python扩展模块。NumPy库提供了快速的预编译函数来执行各种数学和数值计算。同时,NumPy通过提供非常强大的数据结构丰富了Python编程语言。

我们将重点学习一种称为NumPy数组的数据结构。这个数据结构非常强大,有助于高效计算多维数组和矩阵。

在标准的Python数据结构中,我们有列表(list)、元组(tuple)、字典(dictionary)和集合(set)。但NumPy引入了数组的概念,这完全将Python处理数据的能力提升到了一个新的高度。我们将在后续内容中详细探讨NumPy数组。

你可以打开Jupyter笔记本来进行实践,以便更好地理解和记录。

NumPy的特点与能力

NumPy的实现旨在处理非常庞大的矩阵和数组。此外,该模块提供了一个庞大的高级数学函数库来操作这些矩阵。你可以查看其官方文档以了解其功能的广度。

如果你访问docs.scipy.org并查看NumPy文档,你会发现其内容非常丰富。NumPy手册包含用户指南、参考手册等。它涵盖了数组对象、通用函数、各种例程(如排序、搜索、计数、统计)、集合操作、多项式、填充数组、输入输出、线性代数、逻辑函数、金融函数、函数式编程、数据类型例程、日期时间支持函数、字符串操作等众多主题。当然,我们不会覆盖所有内容,只会介绍其中一部分。

许多第三方应用都构建在NumPy之上。例如,SciPy库就是基于NumPy构建的。另一个重要的库Pandas同样构建于NumPy之上。Pandas利用NumPy的数组数据结构,构建了自己的两种数据结构:Series(序列)DataFrame(数据框),它们功能全面,非常强大。

随着课程的深入,我们将详细研究所有这些内容,并学习如何基于它们进行开发。

如果你想在Jupyter笔记本中使用这些库,只需打开笔记本并导入相应的库即可开始工作。


本节课中,我们一起学习了数据分析在物联网场景下的必要性,并初步认识了Python数据分析生态的核心库NumPy。我们了解了NumPy的定义、其强大的数组数据结构,以及它作为SciPy和Pandas等高级库基础的重要地位。在接下来的课程中,我们将开始动手实践,深入学习NumPy的具体用法。

031:温度转换编程 🔥

在本节课中,我们将学习如何将Jupyter Notebook与PyCharm集成,并使用NumPy库进行高效的数据处理。我们将通过一个具体的例子——将摄氏温度列表转换为华氏温度——来对比Python原生列表与NumPy数组在性能和操作上的差异。


连接Jupyter Notebook到PyCharm

上一节我们介绍了NumPy的基本概念,本节中我们来看看如何在PyCharm中连接并使用Jupyter Notebook进行交互式编程。

你可以从PyCharm连接到Jupyter Notebook的令牌。如果无法从常规界面获取令牌,可以采取以下步骤。

以下是获取令牌的步骤:

  1. 在浏览器中打开Jupyter Notebook。
  2. 在“更多工具”选项中,找到名为“检查元素”的功能。
  3. 按下键盘上的 F12 键。
  4. 在开发者工具中,你可以找到Jupyter Notebook的令牌。
  5. 复制该令牌。

复制令牌后,返回PyCharm。

在PyCharm中创建一个新的Jupyter Notebook。将其命名为“numpy”。虽然我们再次使用“numpy”这个名字,但这是一个Jupyter Notebook文件,所以没有关系。

为了测试连接,我们可以先运行一个简单的命令。

print("hello")

运行代码以检查连接。系统会提示输入令牌。粘贴你之前复制的令牌,然后点击确认以连接到Jupyter Notebook服务器。连接成功后,代码会运行并打印出结果。

现在你可以在Jupyter Notebook中使用NumPy,并在此处直接执行操作。


导入NumPy并创建数组

在开始理解NumPy数组的强大功能之前,我们需要先导入它。

首先,在Jupyter Notebook中导入NumPy模块,并为其设置一个常用的别名。

import numpy as np

你可以使用 Shift + Enter 来运行当前单元格的代码,也可以点击运行按钮。运行上述代码后,NumPy模块就被成功导入了。


列表与NumPy数组的对比

我们已经成功导入了NumPy模块。现在,在深入理解NumPy数组的伟大之处及其工作原理之前,我们需要先了解NumPy数组和Python列表之间的区别。

假设我有一个温度列表,这些温度值以摄氏度为单位。我的温度传感器位于某个远程环境位置,不断发送温度值,我正在记录这些值。

我得到的温度值单位是摄氏度。让我现在记录一些示例值。

# 这是一个Python列表,存储摄氏温度
temps_celsius_list = [20.1, 20.8, 21.9, 22.5, 22.7, 22.3, 21.2, 20.9, 20.1]

这些是我从某个IoT设备获得的一系列温度值,它们存储在一个列表中。


任务:温度单位转换

我的任务是,将这些温度值从摄氏度转换为华氏度。

将温度从摄氏度转换为华氏度。

我需要将它们从摄氏度转换为华氏度,这会涉及到一些计算。

如果你还记得,华氏温度的计算公式总是:摄氏温度乘以9,除以5,然后加上32。

用公式表示就是:
T(°F) = T(°C) × 9/5 + 32

现在,我需要得到一个新的列表。这个新列表将为原始列表中的每个摄氏温度值存储对应的华氏温度值。我的任务是将每个摄氏温度值转换为对应的华氏温度值。

那么,如何从一个存储摄氏温度的列表,创建一个包含对应华氏温度的新列表呢?这就是我的任务。

我们可以使用一种叫做“列表推导式”的方法。如果只是简单的数学运算,列表推导式是一种直接的方式。

# 使用列表推导式进行转换
temps_fahrenheit_list = [(temp * 9/5) + 32 for temp in temps_celsius_list]
print(temps_fahrenheit_list)

本节课中我们一起学习了如何在PyCharm中集成Jupyter Notebook,导入了NumPy库,并通过一个温度转换的例子,初步对比了使用Python列表和即将学习的NumPy数组处理数据的不同思路。下一节,我们将深入探讨NumPy数组如何更高效、更简洁地完成这类计算任务。

数据分析高级Python:P32:使用列表推导式与NumPy进行温度转换 📊

在本节课中,我们将学习如何使用Python的列表推导式和NumPy库来高效地进行温度转换,并初步了解如何使用Matplotlib进行数据可视化。

上一节我们介绍了数据处理的基本概念,本节中我们来看看如何将摄氏温度列表转换为华氏温度。

使用列表推导式进行转换

首先,我们有一个包含摄氏温度的列表。

temp = [21.1, 23.5, 20.6, 22.1, 25.0, 19.8, 18.9, 24.2, 22.7]

我们的目标是为列表中的每个元素创建一个新列表。新列表中的每个元素由原列表中的对应元素通过公式转换而来。

转换公式为:华氏温度 = 摄氏温度 × 9 / 5 + 32

以下是使用列表推导式完成此操作的方法:

fahrenheit = [element * 9 / 5 + 32 for element in temp]

这段代码会遍历 temp 列表中的每个元素,对每个元素执行公式计算,并将结果收集到一个名为 fahrenheit 的新列表中。打印 fahrenheit 列表将显示所有转换后的华氏温度值。

使用NumPy数组进行转换

现在,让我们看看如何使用NumPy库以更高效的方式完成相同的任务。首先,我们需要从列表创建一个NumPy数组。

import numpy as np
c = np.array(temp)

打印 c 的类型,你会发现它不再是普通的Python列表,而是一个 numpy.ndarray 对象。NumPy数组的优势在于可以对整个数组执行操作,而无需显式地遍历每个元素。

要从摄氏温度数组创建华氏温度数组,可以直接对整个数组应用转换公式:

f = c * 9 / 5 + 32

打印 f,你会得到与列表推导式相同的结果。NumPy在底层进行了优化,这种向量化操作通常比循环或列表推导式在时间和空间上更高效。

使用Matplotlib进行数据可视化

数据转换后,我们常常希望将其可视化。Matplotlib是Python中一个强大的绘图库。

首先,导入Matplotlib的pyplot模块:

import matplotlib.pyplot as plt

要绘制摄氏温度随时间变化的折线图,可以使用以下代码:

plt.plot(temp)
plt.show()

这将在新窗口中显示一个简单的折线图,其中X轴是数据点的索引(0, 1, 2...),Y轴是温度值。

如果我们希望X轴代表具体的日期,可以提供一个日期列表:

days = [22, 23, 24, 25, 26, 27, 28, 29, 30]
plt.plot(days, temp)
plt.show()

现在,图表将在X轴上显示日期,在Y轴上显示对应的温度值,使得数据在时间维度上的变化一目了然。

Matplotlib的pyplot模块提供了一个类似MATLAB的、过程化的绘图接口,但它完全免费且开源,功能强大。

总结

本节课中我们一起学习了:

  1. 使用列表推导式 [element * 9 / 5 + 32 for element in temp] 进行元素级转换。
  2. 使用NumPy数组进行向量化运算 c * 9 / 5 + 32,这种方法在处理大型数据集时效率更高。
  3. 使用Matplotlibplt.plot()plt.show() 函数对转换后的数据进行基本可视化,并可以自定义坐标轴标签。

通过结合这些工具,我们可以更高效地处理和分析数据,并为后续学习更复杂的数据科学和物联网应用打下基础。在接下来的课程中,我们将更深入地探讨NumPy、Matplotlib以及Pandas等库。

033:NumPy入门 🚀

在本节课中,我们将深入学习NumPy库。我们将探讨NumPy数组与Python列表的核心区别,并了解为何NumPy在数据科学中至关重要。

启动Jupyter Notebook环境

上一节我们介绍了数据分析库的概览,本节中我们来看看如何开始使用NumPy。首先,我们需要在Jupyter Notebook中设置环境。

导入NumPy库,通常使用别名np

import numpy as np

为了验证环境配置正确,可以执行一个简单的打印命令:

print("Hello")

NumPy数组与Python列表的对比

现在,我们来探讨一个核心问题:既然Python已经提供了列表,为什么还需要使用NumPy数组?

首先,创建一个Python列表和一个NumPy数组进行对比:

# 创建一个Python列表
lst = [24, 12, 57]
print(type(lst))  # 输出: <class 'list'>

# 将列表转换为NumPy数组
np_arr = np.array(lst)
print(type(np_arr))  # 输出: <class 'numpy.ndarray'>

从表面上看,两者存储和访问数据的方式相似。例如,都可以通过索引访问元素:

print(lst[0])      # 输出: 24
print(np_arr[-1])  # 输出: 57

NumPy数组的优势

那么,NumPy数组相比列表有何优势?我们上次课程讨论过,NumPy支持向量化操作,无需显式循环。

以下是两种方式的对比:

# 使用列表推导式对列表每个元素进行平方
lst_squared = [x**2 for x in lst]

# 使用NumPy对数组每个元素进行平方(向量化操作)
np_arr_squared = np_arr ** 2

向量化操作是NumPy的第一个主要优势,它使代码更简洁、执行更高效。

内存消耗对比

除了向量化操作,NumPy数组与列表在内存消耗上也有显著差异。本节我们来比较两者的内存使用情况。

首先,讨论列表的内存消耗。我们可以使用sys模块的getsizeof函数来查看对象的内存占用。

以下是查看内存占用的方法:

from sys import getsizeof as size

# 计算列表的内存占用
list_memory = size(lst)
print(f"列表内存占用: {list_memory} 字节")

接下来,我们看看NumPy数组的内存消耗。NumPy数组在底层使用连续的内存块存储同类型数据,这通常比Python列表更节省内存。

以下是计算数组内存占用的示例:

# 计算NumPy数组的内存占用
array_memory = np_arr.nbytes
print(f"NumPy数组内存占用: {array_memory} 字节")

通过对比可以发现,对于大型数据集,NumPy数组的内存效率远高于Python列表,这是其在处理大规模数值数据时的第二个关键优势。

总结

本节课中我们一起学习了NumPy的入门知识。我们比较了NumPy数组与Python列表,重点探讨了NumPy在向量化运算和内存效率方面的核心优势。理解这些差异是高效进行数据分析和科学计算的基础。在接下来的课程中,我们将继续探索NumPy更强大的功能。

数据分析高级Python:构建与优化:p34:列表内存占用分析 🧠

在本节课中,我们将学习如何分析Python列表在内存中的占用情况。我们将通过计算列表本身及其元素的内存大小,来理解Python列表的内存结构。


上一节我们介绍了内存分析的基本概念,本节中我们来看看如何具体计算一个列表的内存占用。

首先,我们创建一个包含三个整数的列表,并将其存储在一个变量中。

lst = [10, 20, 30]

接下来,我们使用sys模块中的getsizeof函数来获取这个列表对象本身的内存大小。

import sys
size_of_list = sys.getsizeof(lst)
print("Size of list:", size_of_list)

执行上述代码,输出结果为88。这意味着存储这个列表结构本身需要88字节。

现在产生一个问题:这个88字节是列表的完整大小吗?它是否包含了列表中三个元素的实际数据?为了理解这一点,我们先创建一个空列表并查看其大小。

empty_lst = []
size_of_empty_list = sys.getsizeof(empty_lst)
print("Size of empty list:", size_of_empty_list)

执行代码,输出结果为64。这表明一个空列表本身就有64字节的固定开销。

通过计算差值,我们可以得出每个列表元素引用所占用的内存。

  • 列表总大小:88字节
  • 空列表大小:64字节
  • 三个元素引用总大小:24字节 (88 - 64)
  • 每个元素引用大小:8字节 (24 / 3)

然而,在Python中,列表存储的并不是元素值本身,而是指向这些值(对象)的引用。我们可以验证这一点。

print(type(lst[0]))

输出结果为<class 'int'>,证实lst[0]是一个指向整数对象的引用。

因此,之前计算的8字节是存储这个“引用”或“指针”的成本,而不是整数对象本身的大小。要获取整数对象实际占用的内存,我们需要测量该元素本身。

size_of_each_element = sys.getsizeof(lst[0])
print("Size of each element (object):", size_of_each_element)

执行代码,输出结果为28。这意味着每个整数对象本身占用28字节。

现在,我们可以计算这个列表在内存中的总消耗。它由两部分组成:

  1. 列表结构本身的开销(包含引用数组)。
  2. 所有元素对象本身的大小。

以下是计算总内存占用的公式:

total_size = size_of_list + (len(lst) * size_of_each_element)
print("Total memory consumed by list:", total_size)

对于我们的例子,总大小为 88 + (3 * 28) = 172 字节。


本节课中我们一起学习了Python列表的内存占用分析。我们了解到:

  1. 列表对象有固定的内存开销(例如64字节)。
  2. 列表中的每个元素占用额外的8字节来存储指向实际对象的引用。
  3. 元素对象(如整数)本身有独立的内存占用。
  4. 列表的总内存消耗是列表结构大小所有元素对象大小之和的总和。

理解这一点对于优化使用大量列表的数据分析程序至关重要。在下一节,我们将把这种分析方法应用到NumPy数组上,并进行对比。

035:NumPy数组占用的内存与性能对比 🧠⚡

在本节课中,我们将要学习NumPy数组与Python列表在内存占用和计算性能上的核心差异。我们将通过具体的代码示例来量化这些差异,帮助你理解为什么NumPy在数据科学领域如此重要。

概述

NumPy是Python中进行科学计算的基础库,其核心是ndarray对象。与Python内置的列表相比,NumPy数组在内存使用效率和计算速度上具有显著优势。本节我们将通过对比实验来直观展示这些优势。

NumPy数组的内存占用

上一节我们介绍了NumPy数组的基本概念,本节中我们来看看其内存占用的具体表现。

NumPy数组不包含对Python对象的引用,而是直接存储数值本身。这意味着数组占用的内存就是其数据本身的大小,没有额外的开销。

以下是计算Python列表和NumPy数组内存占用的方法:

import numpy as np
import sys

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_28.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_29.png)

# 创建一个Python列表
py_list = [24, 1257, 57, 100, 200, 300]
print(f"Python列表: {py_list}")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_31.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_33.png)

# 计算Python列表的内存占用
size_of_list = sys.getsizeof(py_list)
print(f"Python列表的内存占用: {size_of_list} 字节")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_35.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_37.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_38.png)

# 将列表转换为NumPy数组
np_array = np.array(py_list)
print(f"NumPy数组: {np_array}")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_40.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_42.png)

# 计算NumPy数组的内存占用
size_of_np_array = np_array.nbytes
print(f"NumPy数组的内存占用: {size_of_np_array} 字节")

运行上述代码,你会发现即使对于少量元素,NumPy数组的内存占用也远小于Python列表。随着元素数量的增加,这种差异会变得更加显著。

性能时间对比

除了内存优势,NumPy在计算速度上也远超Python列表。这是因为NumPy的底层由C语言实现,并且支持向量化操作,避免了Python循环的开销。

以下是进行时间对比的步骤:

  1. 首先,我们需要导入必要的模块。
  2. 然后,创建两个函数,分别使用Python循环和NumPy向量化操作执行相同的计算任务。
  3. 最后,使用time模块测量并比较两个函数的执行时间。

以下是具体的代码实现:

import numpy as np
import time

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_65.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_66.png)

def calculate_time_python_list(size=1000000):
    """使用Python列表和循环进行计算"""
    x = list(range(size))
    y = list(range(size))
    z = []
    start_time = time.time()
    for i in range(len(x)):
        z.append(x[i] + y[i])
    end_time = time.time()
    return end_time - start_time

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_68.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_69.png)

def calculate_time_numpy_array(size=1000000):
    """使用NumPy数组进行向量化计算"""
    x = np.arange(size)
    y = np.arange(size)
    start_time = time.time()
    z = x + y  # 向量化操作,无需循环
    end_time = time.time()
    return end_time - start_time

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_71.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_73.png)

# 执行时间对比
py_time = calculate_time_python_list(1000000)
np_time = calculate_time_numpy_array(1000000)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_75.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/c37408bbc5730d68ebadffe663143d5e_77.png)

print(f"Python列表计算耗时: {py_time:.4f} 秒")
print(f"NumPy数组计算耗时: {np_time:.4f} 秒")
print(f"NumPy比Python列表快 {py_time / np_time:.1f} 倍")

运行这段代码,你将看到NumPy的向量化操作比等价的Python循环快几个数量级。这种性能提升在处理大规模数据集时至关重要。

总结

本节课中我们一起学习了NumPy数组与Python列表的核心差异:

  1. 内存效率:NumPy数组直接在连续的内存块中存储数据,内存占用远低于存储对象引用的Python列表。其大小可通过numpy.ndarray.nbytes属性获得。
  2. 计算性能:NumPy利用预编译的C代码和向量化操作,在执行数学运算时比使用Python循环的列表快得多。

因此,在进行数值计算和数据分析时,应优先使用NumPy数组,以获得更高的内存利用率和更快的执行速度。

数据分析高级Python:P36:Python列表与NumPy数组性能对比 🚀

在本节课中,我们将学习如何通过一个简单的性能对比实验,直观地理解Python原生列表与NumPy数组在执行效率上的显著差异。我们将编写两个函数,分别使用列表和NumPy数组进行元素加法运算,并测量其执行时间。


概述

我们将创建两个函数:一个使用Python原生列表进行循环加法,另一个使用NumPy数组进行向量化加法。通过对比两者的执行时间,可以清晰地看到NumPy在数值计算上的高效性。


Python列表版本实现

首先,我们来实现使用Python列表进行元素加法的函数。其核心思路是遍历两个列表,将对应位置的元素相加,并将结果存入一个新列表。

以下是该函数的具体步骤:

  1. 记录操作开始前的时间 t1
  2. 创建两个包含1000个元素的列表 xy
  3. 创建一个空列表 z,用于存放结果。
  4. 使用 for 循环遍历索引,将 x[i]y[i] 相加,结果追加到列表 z 中。
  5. 记录操作结束后的时间 t2
  6. 函数返回时间差 t2 - t1,即整个操作所耗费的时间。

对应的代码逻辑如下:

def calculate_time_python():
    import time
    t1 = time.time()
    x = list(range(1000))
    y = list(range(1000))
    z = []
    for i in range(len(x)):
        z.append(x[i] + y[i])
    t2 = time.time()
    return t2 - t1


NumPy数组版本实现

上一节我们介绍了使用Python列表进行计算的笨拙方法。本节中,我们来看看如何使用NumPy数组以更高效、简洁的方式完成同样的任务。

以下是该函数的具体步骤:

  1. 记录操作开始前的时间 t1
  2. 使用 np.arange(1000) 直接创建两个包含1000个元素的NumPy数组 xy。这比先创建列表再转换更高效。
  3. 利用NumPy的向量化操作,直接使用 z = x + y 完成整个数组的加法,无需显式循环。
  4. 记录操作结束后的时间 t2
  5. 函数返回时间差 t2 - t1

对应的代码逻辑如下:

def calculate_time_numpy():
    import numpy as np
    import time
    t1 = time.time()
    x = np.arange(1000)
    y = np.arange(1000)
    z = x + y  # 向量化加法,无需循环
    t2 = time.time()
    return t2 - t1


执行与结果对比

定义好两个函数后,我们可以调用它们并打印执行时间。由于单次执行时间可能太短(尤其是NumPy版本),为了更清晰地观察差异,我们可以将测试放在一个循环中多次运行。

以下是执行对比的示例代码:

# 单次调用对比
print("NumPy 时间:", calculate_time_numpy())
print("Python 列表时间:", calculate_time_python())

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/5b411cec3b5349ef62c69524f2488cff_47.png)

print("\n--- 循环测试10次 ---")
# 循环测试,取平均值更可靠
for i in range(10):
    t_numpy = calculate_time_numpy()
    t_python = calculate_time_python()
    print(f"第{i+1}次 - NumPy: {t_numpy:.6f}秒, Python列表: {t_python:.6f}秒")

运行上述代码,你会发现 calculate_time_numpy 函数的执行时间几乎总是远小于 calculate_time_python 函数。即使将循环次数增加到1000次,NumPy版本的速度优势依然非常明显。


总结

本节课中我们一起学习了如何通过实际代码对比Python列表与NumPy数组的性能。实验清晰地表明:

  1. 时间效率:NumPy数组利用向量化计算和底层C语言实现,在执行数值运算时比使用Python循环操作列表要快几个数量级。
  2. 代码简洁性:NumPy语法更简洁(如 x + y),避免了冗长的循环代码,提高了开发效率。
  3. 存储效率:NumPy数组在内存中连续存储同类型数据,比Python列表(存储对象引用)更节省空间。

正是由于在时间空间上的双重高效性,NumPy成为了科学计算和数据分析领域不可或缺的核心工具。

037:创建NumPy数组 🧮

在本节课中,我们将学习如何创建NumPy数组。NumPy数组是进行高效数值计算的基础,它比Python原生列表更节省空间,计算速度也更快。我们将介绍两种创建数组的核心函数:arangelinspace

上一节我们了解了NumPy的优势,本节中我们来看看如何实际创建NumPy数组。

使用 arange 函数创建数组

arange 函数用于在给定的区间内返回均匀间隔的值。其基本语法是 np.arange(start, stop, step)

以下是使用 arange 函数创建数组的几种方式:

  • 指定起始和结束值np.arange(1, 10) 会创建一个从1(包含)到10(不包含)的整数数组。
    import numpy as np
    a = np.arange(1, 10)
    print(a)  # 输出: [1 2 3 4 5 6 7 8 9]
    

  • 仅指定结束值np.arange(10) 会创建一个从0(默认起始值)到9的整数数组。
    a = np.arange(10)
    print(a)  # 输出: [0 1 2 3 4 5 6 7 8 9]
    

  • 指定步长np.arange(0.3, 1.5, 0.7) 会创建一个从0.3开始,以0.7为步长递增,直到小于1.5的浮点数数组。
    a = np.arange(0.3, 1.5, 0.7)
    print(a)  # 输出: [0.3 1. ]
    

  • 创建递减数组:通过设置负的步长,可以创建递减的数组,例如 np.arange(10.5, 0.5, -2)
    a = np.arange(10.5, 0.5, -2)
    print(a)  # 输出: [10.5  8.5  6.5  4.5  2.5]
    

使用 linspace 函数创建数组

linspace 函数用于在指定的起始值和结束值之间,返回指定数量的、等间距的数据点。其基本语法是 np.linspace(start, stop, num)

以下是 linspace 函数的特点和用法:

  • 默认生成50个点:如果不指定 num 参数,np.linspace(1, 10) 默认会在1到10之间生成50个等间距的点。
    a = np.linspace(1, 10)
    print(a)  # 输出一个包含50个元素的数组
    print(type(a))  # 输出: <class 'numpy.ndarray'>
    

  • 指定生成点的数量np.linspace(1, 10, 7) 会在1到10之间生成7个等间距的点。
    a = np.linspace(1, 10, 7)
    print(a)  # 输出: [ 1.   2.5  4.   5.5  7.   8.5 10. ]
    
    可以看到,相邻点之间的差值(如2.5-1=1.5, 4-2.5=1.5)是相等的。

本节课中我们一起学习了创建NumPy数组的两种主要方法:使用 arange 函数生成按固定步长变化的序列,以及使用 linspace 函数生成指定数量的等间距点。理解并掌握这些方法是高效使用NumPy进行数据分析的第一步。

038:不同维度的NumPy数组 📊

在本节课中,我们将学习如何创建不同维度的NumPy数组。我们将从零维数组开始,逐步过渡到一维、二维乃至更高维度的数组,并学习如何查看数组的维度和形状。


零维数组

零维数组本质上就是一个单一的值。它是最简单的NumPy数组形式。

首先,我们需要导入NumPy模块。

import numpy as np

接下来,我们创建一个零维数组。使用np.array()方法,并传入一个单一的值。

x = np.array(42)
print(x)
print(type(x))
print("维度:", np.ndim(x))

运行上述代码,输出结果如下:

  • x的值是42
  • x的类型是numpy.ndarray
  • x的维度是0

零维数组在实际数据分析中不常用,但它是理解数组维度的基础。


一维数组

上一节我们介绍了零维数组,本节中我们来看看一维数组。一维数组类似于一个列表,包含一系列有序的元素。

以下是创建一维数组的步骤:

  1. 创建一个整数类型的一维数组。
  2. 创建一个浮点数类型的一维数组。
  3. 打印数组及其属性。

# 创建整数数组
a = np.array([1, 2, 3])
# 创建浮点数数组
b = np.array([1.1, 2.5, 6.3])

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/db1bfafa2f8831d2e1d408ad67930dfc_16.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/db1bfafa2f8831d2e1d408ad67930dfc_17.png)

print("数组 a:", a)
print("数组 b:", b)
print("a 的数据类型:", a.dtype)
print("b 的数据类型:", b.dtype)
print("a 的维度:", np.ndim(a))
print("b 的维度:", np.ndim(b))

除了使用np.ndim()函数,我们还可以通过数组对象的.ndim属性来获取维度。

print("a 的维度 (使用属性):", a.ndim)

二维及多维数组

理解了零维和一维数组后,现在我们来探讨二维及多维数组。二维数组可以看作是一个“列表的列表”,即矩阵。

以下是创建二维数组的方法:

# 创建一个二维数组(矩阵)
A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/db1bfafa2f8831d2e1d408ad67930dfc_23.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/db1bfafa2f8831d2e1d408ad67930dfc_25.png)

print("二维数组 A:")
print(A)
print("A 的维度:", A.ndim)

输出显示A是一个3x3的矩阵,其维度为2

那么,我们能否创建三维数组呢?答案是肯定的。三维数组可以理解为“列表的列表的列表”。

# 创建一个三维数组
A_3d = np.array([[[1, 2], [3, 4]],
                 [[5, 6], [7, 8]],
                 [[9, 10], [11, 12]]])

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/db1bfafa2f8831d2e1d408ad67930dfc_29.png)

print("三维数组 A_3d:")
print(A_3d)
print("A_3d 的维度:", A_3d.ndim)

在这个例子中,我们创建了一个结构更复杂的三维数组,其维度为3。通过这种方式,我们可以构建任意维度的数组。


数组的形状

除了维度,数组的形状也是一个重要属性。形状描述了数组在每个维度上的大小。

我们可以使用.shape属性来查看数组的形状。

print("一维数组 a 的形状:", a.shape)
print("二维数组 A 的形状:", A.shape)
print("三维数组 A_3d 的形状:", A_3d.shape)

对于一维数组a = [1, 2, 3],其形状是(3,),表示它有3个元素。
对于二维数组A,其形状是(3, 3),表示它是一个3行3列的矩阵。
对于三维数组A_3d,其形状是(3, 2, 2),表示它由3个2x2的矩阵组成。

理解形状有助于我们掌握数组的数据结构,为后续的数据操作和计算打下基础。


本节课中我们一起学习了如何创建和识别不同维度的NumPy数组,包括零维、一维、二维和三维数组。我们还学习了如何通过.ndim属性查看数组的维度,以及通过.shape属性查看数组的形状。这些基本概念是使用NumPy进行高效数据分析的基石。

039:数组形状与索引切片 🧮

在本节课中,我们将要学习NumPy数组的两个核心概念:形状索引切片。理解数组的形状是进行数据重塑和维度操作的基础,而掌握索引切片则是高效访问和操作数组数据的关键。

上一节我们介绍了NumPy数组的基本创建,本节中我们来看看如何查看和改变数组的形状。

数组形状

数组的形状描述了其维度结构,即每个轴(维度)上有多少个元素。对于一个二维数组,形状通常表示为 (行数, 列数)。形状信息以一个元组的形式返回。

获取数组形状有两种常用方法:

  1. 使用 np.shape(数组对象) 函数。
  2. 使用数组对象的 .shape 属性。

以下是查看数组形状的示例:

import numpy as np

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_17.png)

# 创建一个二维数组
x = np.array([[67, 12, 15],
              [77, 45, 12],
              [45, 12, 15],
              [18, 78, 98],
              [45, 50, 12]])

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_19.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_20.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_21.png)

# 方法一:使用 np.shape() 函数
shape_via_function = np.shape(x)
print(f"通过函数获取的形状: {shape_via_function}")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_23.png)

# 方法二:使用 .shape 属性
shape_via_attribute = x.shape
print(f"通过属性获取的形状: {shape_via_attribute}")

运行上述代码,输出结果将是 (5, 3),表示这个数组有5行和3列。

改变数组形状

我们可以改变一个现有数组的形状,前提是新形状的总元素数必须与原数组的总元素数保持一致。这个操作通过直接修改数组的 .shape 属性来完成。

以下是改变数组形状的步骤和示例:

  1. 计算总元素数:原数组形状为 (5, 3),总元素数为 5 * 3 = 15
  2. 选择新形状:新形状也必须是两个整数的乘积等于15,例如 (3, 5)(15, 1)
  3. 执行重塑操作:直接为 .shape 属性赋值。
# 将形状从 (5, 3) 改为 (3, 5)
x.shape = (3, 5)
print("重塑为 (3, 5) 后的数组:")
print(x)
print(f"新形状: {x.shape}")

重要限制:如果尝试重塑为不兼容的形状(例如,将15个元素的数组重塑为 (4, 4),因为 4*4=16 ≠ 15),NumPy会抛出 ValueError 错误。

# 这将导致错误,因为 4*4=16 不等于原数组的15个元素
# x.shape = (4, 4)  # ValueError: cannot reshape array of size 15 into shape (4,4)

通过改变形状,我们可以将数据重新组织成更适合后续计算(如矩阵乘法)或可视化(如将一维数据转为图像)的格式。

上一节我们介绍了如何查看和改变数组的整体形状,本节中我们来看看如何访问数组内部的单个或部分元素,即索引和切片。

数组的索引与切片

索引用于访问数组中的单个元素,而切片用于获取数组的一个子集。其语法与Python列表非常相似,但可以同时应用于多个维度。

访问行与列

对于一个二维数组,我们可以通过指定行索引来访问整行。

# 假设 x 目前是形状为 (3, 5) 的数组
# 访问第0行(索引从0开始)
row_0 = x[0]
print(f"第0行: {row_0}")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_38.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_39.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_40.png)

# 访问第2行
row_2 = x[2]
print(f"第2行: {row_2}")

访问特定元素

要访问数组中的特定元素,需要提供行索引和列索引,格式为 数组名[行索引, 列索引]

例如,要访问上面 row_2(即 x[2])中的第2个元素(假设是 15),其列索引为 2

# 访问 x 中第2行、第2列的元素(行和列索引都从0开始)
element = x[2, 2]
print(f"x[2, 2] 的元素是: {element}")

这等价于先获取行,再从行中获取列:x[2][2]。但 x[2, 2] 是更高效和常用的写法。

切片操作

切片允许我们获取行或列的一部分。语法是 start:stop:step

以下是切片操作的示例:

# 获取前两行的所有列
sub_array_rows = x[0:2, :]
print("前两行:")
print(sub_array_rows)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_62.png)

# 获取所有行的第1到第3列(索引1到3,不包含3)
sub_array_cols = x[:, 1:4]
print("\n所有行的第1至第3列:")
print(sub_array_cols)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_64.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/d3d0165531238d476a510ec86e1c70fc_65.png)

# 获取一个具体的子区域:第0-1行,第2-4列
sub_region = x[0:2, 2:5]
print("\n第0-1行,第2-4列的子区域:")
print(sub_region)

本节课中我们一起学习了

  1. 数组形状:使用 .shape 属性或 np.shape() 函数获取表示数组维度的元组。
  2. 重塑数组:通过直接赋值给 .shape 属性来改变数组形状,但必须确保新形状的总元素数不变。
  3. 索引:使用 array[row_index, col_index] 的格式访问特定元素。
  4. 切片:使用 start:stop:step 语法获取数组的子集,可以灵活应用于行、列或任意子区域。

掌握这些操作是进行高效数组数据处理和准备的关键步骤。在接下来的课程中,我们将利用这些基础,学习更强大的数组操作和计算方法。

数据分析高级Python:P40:NumPy数组的索引与切片 🧮

在本节课中,我们将要学习NumPy数组的索引与切片操作。索引用于访问数组中的单个元素,而切片则用于获取数组的一个子集。理解这两种操作对于高效处理数据至关重要。

上一节我们介绍了NumPy数组的基本概念,本节中我们来看看如何具体访问和提取数组中的数据。

低效的索引方式

以下方式不是一种高效的做法。

因为 x[2][2] 这种写法等同于以下步骤:

  1. 首先,x[2] 会创建一个临时变量。
    temp = x[2]
    
  2. 然后,从这个临时变量中索引第二个元素。
    a = temp[2]
    
  3. 最终,变量 a 的值是 15

这种做法效率不高。它会在内存和时间效率方面造成问题。

高效的NumPy索引方式

NumPy提供了一种更好的方式。使用 x[2, 2] 可以直接获取值,而无需创建临时变量。

这就是获取数组中特定值的方法。

负索引的使用

负索引允许从数组末尾开始计数。

例如,x[-1] 将返回最后一行。
x[-1, 2] 将返回最后一行的第三个元素(索引为2),其值为 58

综合索引示例

可以组合使用正负索引。

  • x[-1, -1] 返回最后一个元素 40
  • x[1, -1] 返回第二行(索引1)的最后一列(索引-1),其值为 19
  • x[1, -2] 返回第二行的倒数第二列,其值为 45

以下是列索引的对应关系:

  • 正向索引:0, 1, 2, 3, 4, 5
  • 反向索引:-6, -5, -4, -3, -2, -1

这就是NumPy数组中索引的工作方式。

一维数组切片

在学习了索引之后,接下来我们看看切片操作。切片是Python和NumPy中一个非常巧妙的概念。

切片操作使用冒号 : 运算符。

为了让理解更清晰,我们先从一维数组开始。

假设有一个一维数组 y

y = np.array([10, 20, 30, 40, 50, 2, 7])
  • y[5] 返回索引5的元素 2
  • 如果想切片获取 [40, 50, 2] 这部分(索引3到5),需要使用 y[3:6]。注意,起始索引3是包含的,而结束索引6是排除的。
  • y[:6] 会从第一个元素(索引0)开始,切片到索引6(排除)为止。
  • y[3:] 会从索引3开始,切片到最后一个元素。

这就是一维数组的切片,它可以获取数组的特定部分。

二维数组切片

现在,让我们看看切片在二维数组中如何工作。

假设我们想从数组 x 中提取一个矩形区域,例如包含元素 45, 78, 15, 18 的2x2子矩阵。

我们需要指定行和列的范围:

  • :我们想要第一行和第二行(索引1和2)。切片写法是 1:3(因为结束索引3是排除的)。
  • :我们想要第二列和第三列(索引2和3)。切片写法是 2:4

因此,完整的切片操作是 x[1:3, 2:4]。这将返回一个新的NumPy数组,即我们想要的子矩阵。

通过切片,我们可以从原数组中提取出需要的部分。

切片的高级用法

我们可以省略切片的起始或结束索引。

  • x[:3, 2:4]:行从开始到索引2(3-1),列从索引2到3。
  • x[1:, 2:4]:行从索引1到最后,列从索引2到3。
  • x[:, 2:4]:所有行,列从索引2到3。
  • x[2, :]:第三行(索引2)的所有列。
  • x[:, :]:返回整个数组。

带步长的切片

切片还可以指定步长,用于间隔选取元素。

语法是 start:stop:step

  • 例如,x[::2] 会从开始到结束,每隔一行选取一行。
  • x[:, ::2] 会选取所有行,但每行列的步长为2。

本节课中我们一起学习了NumPy数组的索引与切片。索引(如 x[2,2])用于访问单个元素,应避免使用 x[2][2] 这种低效方式。切片(使用 : 运算符)用于提取子数组,可以通过指定起始、结束和步长来灵活控制提取的范围。掌握这些操作是进行高效数组数据处理的基础。

041:切片进阶 🧮

在本节课中,我们将要学习NumPy数组切片的高级用法,特别是如何通过指定步长(step size)来间隔地选取元素。我们将从一维数组开始,逐步过渡到二维数组,并理解步长参数在切片操作中的具体作用。

上一节我们介绍了基础的切片操作,本节中我们来看看如何通过指定步长来控制切片的间隔。

一维数组的步长切片

首先,我们从一个一维数组 y 开始理解。假设 y 是一个包含多个元素的数组。

基础的切片语法是 y[start:stop],它会选取从索引 startstop-1 的元素。例如,y[3:5] 会选取索引为3和4的元素。

如果使用 y[3:],则会从索引3开始,选取直到数组末尾的所有元素。

现在,我们引入第三个参数:步长。语法变为 y[start:stop:step]step 参数决定了选取元素时的索引增量。

以下是步长切片的具体示例:

  • y[3::2]:从索引3开始,直到末尾,每隔一个元素取一个(步长为2)。这意味着先取索引3的元素,然后索引加2取索引5的元素,再加2取索引7的元素,依此类推。
  • y[3::3]:从索引3开始,直到末尾,每隔两个元素取一个(步长为3)。这意味着先取索引3的元素,然后索引加3取索引6的元素,再加3...如果索引超出范围,则停止。

这就是在一维数组中使用特定步长进行切片的方法。

二维数组的步长切片

理解了在一维数组中的操作后,现在让我们将其应用到二维数组 x 上。

对于二维数组,我们可以在行和列两个维度上分别指定步长。语法格式为 x[行切片, 列切片],其中行切片和列切片都可以包含步长参数。

以下是二维数组步长切片的操作:

  • 对行使用步长x[::2, :]。这表示选取所有行,但步长为2(即每隔一行取一行),同时选取所有列。结果会得到第0行、第2行、第4行...的数据。
  • 对列使用步长x[:, ::2]。这表示选取所有行,但只选取所有列中步长为2的列(即每隔一列取一列)。结果会得到第0列、第2列、第4列...的数据。
  • 同时对行和列使用步长x[::2, ::3]。这表示每隔一行取一行(步长2),并且在取出的这些行中,每隔两列取一列(步长3)。

通过这种方式,你可以灵活地从多维数组中提取有规律间隔的数据子集。

总结

本节课中我们一起学习了NumPy切片操作的进阶技巧——使用步长(step size)。我们掌握了如何在一维和二维数组中通过 start:stop:step 的语法来间隔地选取元素。这项技能对于从大型数据集中规律性地采样或处理数据至关重要,是进行高效数据分析的基础操作之一。接下来,我们将探讨如何在NumPy数组上进行各种算术和数学运算。

042:零一数组的创建与应用 🧮

在本节课中,我们将学习如何使用NumPy库快速创建全零或全一的数组,并探讨一个结合用户输入和数组操作的实际应用问题。

创建全零和全一数组

上一节我们介绍了NumPy数组的基础知识,本节中我们来看看如何快速创建具有特定形状和初始值的数组。NumPy提供了便捷的函数来创建全零或全一的数组。

以下是创建全一数组的代码示例:

import numpy as np
o = np.ones((2, 3))
print(o)

运行上述代码,将得到一个形状为2行3列、元素全为浮点数1的数组。

默认情况下,np.onesnp.zeros 函数创建的是浮点数类型的数组。如果需要整数类型的数组,可以通过 dtype 参数指定。

以下是创建整数类型全一数组的代码:

o = np.ones((2, 3), dtype=int)
print(o)

同样地,我们可以创建全零数组。以下是创建全零数组的代码示例:

z = np.zeros((10, 15), dtype=int)
print(z)

应用实例:根据用户输入创建自定义数组

掌握了创建基础数组的方法后,我们来看一个综合性的应用问题。这个问题将结合用户输入、字符串处理和数组计算。

问题描述如下:从用户处获取一个表示数组形状的字符串(例如 “3,4”),然后创建一个该形状的数组。数组中的每个元素值等于其行号乘以列号。

为了清晰地理解要求,我们来看一个具体例子。如果用户输入 “3,4”,我们需要创建一个3行4列的数组。其元素值计算规则如下:

  • 第0行:所有元素值为 0 * 列号,因此是 [0, 0, 0, 0]
  • 第1行:元素值为 1 * 列号,因此是 [0, 1, 2, 3]
  • 第2行:元素值为 2 * 列号,因此是 [0, 2, 4, 6]

最终生成的数组应为:

[[0 0 0 0]
 [0 1 2 3]
 [0 2 4 6]]

问题解决方案

现在,我们来一步步解决这个问题。以下是实现该功能的完整代码:

import numpy as np

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_70.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_71.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_72.png)

# 1. 获取用户输入
shape_str = input(“请输入数组的形状(例如 3,4): “)

# 2. 处理输入字符串,分割出行数和列数
shape_list = shape_str.split(‘,’)
print(shape_list)  # 例如输入”3,4″,则输出 [‘3’, ‘4’]

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_74.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_75.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_76.png)

# 3. 将字符串转换为整数,并获取行数和列数
rows = int(shape_list[0])
cols = int(shape_list[1])

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_78.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_80.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_81.png)

# 4. 创建一个全零数组作为基础
arr = np.zeros((rows, cols), dtype=int)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_83.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_84.png)

# 5. 使用循环计算每个元素的值(行号 * 列号)
for i in range(rows):          # i 代表行索引(行号)
    for j in range(cols):      # j 代表列索引(列号)
        arr[i, j] = i * j

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_86.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_88.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/ce15de5c0e72402ae730d998c8173ef4_89.png)

# 6. 打印最终结果
print(arr)

代码执行步骤如下:

  1. 使用 input() 函数获取用户输入的形状字符串。
  2. 使用 split(‘,’) 方法将字符串按逗号分割,得到一个字符串列表。
  3. 将列表中的字符串元素转换为整数,分别赋值给 rows(行数)和 cols(列数)。
  4. 使用 np.zeros 创建一个指定形状的全零整数数组作为初始模板。
  5. 使用两层嵌套循环遍历数组的每个位置。外层循环变量 i 是行号,内层循环变量 j 是列号。
  6. 根据规则 元素值 = 行号 * 列号,即 arr[i, j] = i * j,为每个元素赋值。
  7. 打印出最终计算得到的数组。

本节课中我们一起学习了如何使用 np.onesnp.zeros 函数快速创建特定值的数组,并通过一个实际案例,综合运用了输入处理、类型转换和数组操作来解决问题。掌握这些基础但强大的数组创建方法,是进行高效数据分析和科学计算的重要一步。

043:NumPy示例 🧮

在本节课中,我们将学习一个使用NumPy创建特定数组的完整示例。我们将从获取用户输入开始,逐步构建一个数组,其中每个元素的值是其行索引与列索引的乘积。通过这个例子,你将理解如何结合Python基础操作与NumPy数组操作来解决实际问题。

获取用户输入并处理

首先,我们需要从用户那里获取数组的行数和列数。用户输入通常以字符串形式接收,例如“3,4”。我们的任务是解析这个字符串,并将其转换为两个独立的整数。

以下是处理用户输入的步骤:

  1. 使用 input() 函数获取用户输入的字符串。
  2. 使用字符串的 .split() 方法,根据逗号分隔符将字符串分割成一个字符串列表。
  3. 使用列表推导式,将列表中的每个字符串元素转换为整数类型。
  4. 利用Python的列表解包功能,将整数列表中的两个值分别赋值给 rowcolumn 变量。

# 获取用户输入,例如:“3,4”
user_input = input(“Enter the shape of the array (rows,columns): “)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/4ed4c4dd5f66754dc2525157071572be_22.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/4ed4c4dd5f66754dc2525157071572be_23.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/4ed4c4dd5f66754dc2525157071572be_25.png)

# 将输入字符串按逗号分割并转换为整数列表
row, column = [int(element) for element in user_input.split(‘,’)]

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/4ed4c4dd5f66754dc2525157071572be_27.png)

print(f“Row: {row}, Column: {column}”)

创建初始NumPy数组

上一节我们介绍了如何获取用户指定的行数和列数。本节中,我们来看看如何利用这些信息创建一个初始的NumPy数组。

我们将使用 np.ones() 函数创建一个指定形状且元素全为1的数组。你也可以使用 np.zeros() 创建全零数组。为了确保数组元素是整数类型,我们需要显式指定 dtype 参数。

import numpy as np

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/4ed4c4dd5f66754dc2525157071572be_35.png)

# 根据用户输入的行和列,创建一个全为1的整数数组
initial_array = np.ones((row, column), dtype=int)
print(“Initial array of ones:”)
print(initial_array)

根据规则填充数组

现在我们已经有了一个指定形状的数组。接下来,我们需要根据题目要求,用每个元素的行索引乘以列索引的结果来填充这个数组。

我们将使用嵌套的 for 循环来遍历数组的每一个位置。外层循环遍历行索引 i,内层循环遍历列索引 j。对于位置 (i, j),我们将其值设置为 i * j

# 遍历数组的每一行和每一列
for i in range(row):
    for j in range(column):
        # 将数组在(i, j)位置的值设置为行索引i乘以列索引j
        initial_array[i, j] = i * j

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/educba-dtanls-adv-py/img/4ed4c4dd5f66754dc2525157071572be_49.png)

print(“Final array (row_index * column_index):”)
print(initial_array)

运行程序,输入“5,9”,你将得到一个5行9列的数组,其中每个元素都符合 a[i, j] = i * j 的规则。

总结

本节课中我们一起学习了如何构建一个完整的NumPy应用示例。我们从处理原始的用户输入字符串开始,将其转换为可用的整数维度。然后,我们利用这些维度创建了一个初始的NumPy数组。最后,我们通过循环遍历数组的每个位置,并根据行索引和列索引的乘积来填充数组,完成了题目的要求。这个例子展示了将基础的Python编程(输入处理、循环)与NumPy的数组操作相结合来解决数据问题的典型流程。

044:NumPy示例续篇 🔢

在本节课中,我们将继续学习NumPy数组的创建方法,包括如何基于现有数组的形状创建全1或全0数组、如何复制数组以及如何创建单位矩阵(或称恒等矩阵)。这些是进行高效数值计算的基础。

上一节我们介绍了使用np.array()等函数创建数组的基本方法。本节中我们来看看几种更高级的数组创建技巧。

基于现有数组形状创建新数组

有时我们需要创建一个与现有数组形状相同,但元素内容不同的新数组。NumPy为此提供了便捷的函数。

假设已经存在一个数组 x

import numpy as np
x = np.array([2, 5, 18])

现在,我们可以创建一个与 x 形状相同但元素全为1的新数组。以下是具体方法:

X = np.ones_like(x)
print(X)

运行上述代码,X 将是一个形状与 x 相同,但所有元素都为1的数组。np.ones_like() 函数的作用就是创建一个新数组,并用1填充,其形状与传入的数组参数相同。

类似地,我们也可以创建元素全为0的数组:

Y = np.zeros_like(x)
print(Y)

np.zeros_like() 函数会创建一个形状与 x 相同,但所有元素都为0的新数组 Y

复制数组

在数据处理中,我们经常需要复制一个数组,以避免修改原始数据。NumPy数组提供了 copy() 方法来实现深度复制。

以下是复制数组的步骤:

  1. 首先导入NumPy并创建一个数组。
  2. 使用数组的 .copy() 方法创建其副本。

具体代码如下:

import numpy as np
x = np.array([2, 5, 18])
y = x.copy()
print(y)

执行后,y 将是 x 的一个完整副本。对 y 的修改不会影响原始的 x 数组。

创建单位矩阵

单位矩阵是一个方阵,其主对角线上的元素均为1,其余元素均为0。在线性代数运算中非常常用。

NumPy提供了两种主要方法来创建单位矩阵。

第一种是使用 np.identity() 函数。该函数创建一个指定大小的正方形单位矩阵。

# 创建一个4x4的单位矩阵
identity_matrix = np.identity(4)
print(identity_matrix)

np.identity(4) 会生成一个4行4列的单位矩阵。

第二种方法是使用更通用的 np.eye() 函数。这个函数功能更强大,可以创建非方阵的对角矩阵,并可以指定对角线的位置。

# 创建一个5行9列的数组,主对角线为1
eye_matrix = np.eye(5, 9)
print(eye_matrix)

np.eye(5, 9) 创建一个5行9列的数组,从左上角开始的主对角线元素为1。

np.eye() 函数还有一个重要的参数 k,用于控制对角线的位置:

  • k=0(默认):主对角线。
  • k=1:主对角线向上偏移一行的对角线。
  • k=-1:主对角线向下偏移一行的对角线。

例如:

print(np.eye(5, 9, k=1))   # 对角线向上偏移一行
print(np.eye(5, 9, k=-2))  # 对角线向下偏移两行

通过调整 k 值,我们可以轻松创建具有不同对角线位置的矩阵。

本节课中我们一起学习了NumPy中几种实用的数组创建方法:使用 ones_likezeros_like 基于形状快速创建数组,使用 copy() 方法安全地复制数组,以及使用 identity() 和功能更灵活的 eye() 函数来创建单位矩阵或对角矩阵。掌握这些方法能为后续进行数组运算和线性代数计算打下坚实基础。在接下来的课程中,我们将深入探讨NumPy的算术运算。

数据分析高级Python:构建与优化:45:使用缩放器处理NumPy数组 🔢

在本节课中,我们将要学习如何在NumPy数组上进行数值运算。我们将从最简单的标量运算开始,逐步过渡到数组之间的算术运算,并理解NumPy如何高效地处理这些操作。


上一节我们介绍了NumPy数组的基础知识,本节中我们来看看如何对数组进行数值运算。首先,我们将从标量运算开始。

使用标量进行运算

标量运算是指将一个单一的数值(标量)与数组中的每一个元素进行运算。以下是具体步骤:

第一步,导入NumPy库并创建一个数组。

import numpy as np
a = np.array([15, 8, 4, 51])

第二步,将标量 2 与数组 a 相加。

a = a + 2
print(a)

执行上述代码后,数组 a 中的每个元素都会增加 2,结果为 [17, 10, 6, 53]

NumPy支持所有基本的算术运算符。以下是其他运算符的示例:

以下是使用不同运算符的示例:

  • 乘法a * 2 会将每个元素乘以 2
  • 除法a / 2 会将每个元素除以 2,结果为浮点数。
  • 整除a // 2 会得到每个元素除以 2 后的整数部分。
  • 取模a % 2 会得到每个元素除以 2 后的余数(结果为 01)。
  • 幂运算a ** 2 会计算每个元素的平方。

与使用Python列表相比,NumPy的标量运算非常高效。如果要对列表进行相同操作,需要使用循环遍历每个元素,而NumPy则一次性完成所有计算。


了解了标量与数组的运算后,接下来我们看看两个数组之间如何进行算术运算。

对数组进行算术运算

数组间的算术运算是指两个形状相同的数组,其对应位置的元素进行运算。

首先,我们创建两个形状相同的数组 ab

import numpy as np
a = np.array([[11, 12, 13], [21, 22, 23], [31, 32, 33]])
b = np.ones((3, 3), dtype=int)  # 创建一个3x3的全1数组
print(a)
print(b)

现在,我们可以对这两个数组进行运算:

以下是数组间运算的示例:

  • 加法a + b 会将 ab 中对应位置的元素相加。
  • 减法a - b 会将 ab 中对应位置的元素相减。
  • 乘法a * b 执行的是逐元素乘法,即将 ab 中对应位置的元素相乘,而不是矩阵乘法。

这些运算都是逐元素进行的,要求参与运算的数组形状必须一致。


本节课中我们一起学习了NumPy数组的数值运算。我们首先掌握了如何使用标量与数组进行高效运算,包括加、减、乘、除等。然后,我们学习了如何对两个形状相同的数组进行逐元素的算术运算。NumPy的这些特性使其在数据分析和科学计算中比原生Python列表更加高效和便捷。

046:矩阵乘法 🧮

在本节课中,我们将要学习NumPy中矩阵乘法的概念、计算方法及其与普通元素乘法的区别。我们将从二维矩阵乘法开始,逐步扩展到三维数组,并了解如何将NumPy数组转换为矩阵对象以使用不同的乘法规则。

矩阵乘法与元素乘法的区别

上一节我们介绍了NumPy数组的基本算术运算,如加法、减法、乘法和除法,这些运算都是逐元素进行的。本节中我们来看看一种特殊的运算——矩阵乘法

矩阵乘法与普通的逐元素乘法完全不同。在逐元素乘法中,两个形状相同的数组对应位置的元素直接相乘。例如,对于数组 ab

a * b  # 结果为 [a[0]*b[0], a[1]*b[1], ...]

而矩阵乘法的规则是:结果矩阵中第 i 行第 j 列的元素,等于第一个矩阵的第 i 行与第二个矩阵的第 j 列对应元素的乘积之和。

其核心公式为:
C[i, j] = Σ (A[i, k] * B[k, j])
其中,Σ 表示对 k 求和。

二维矩阵乘法计算

让我们通过一个具体例子来理解。假设我们有两个数组:

  • 数组 a[[11, 12, 13]](1行3列)
  • 数组 b[[1], [1], [1]](3行1列)

矩阵乘法的计算过程如下:

  1. a 的第一行 [11, 12, 13]
  2. b 的第一列 [1, 1, 1]
  3. 计算点积:11*1 + 12*1 + 13*1 = 36

因此,结果矩阵的第一个(也是唯一一个)元素是 36

在NumPy中,我们使用 np.dot() 函数或 @ 运算符来进行矩阵乘法。

以下是使用 np.dot() 的代码示例:

import numpy as np
a = np.array([[11, 12, 13]])
b = np.array([[1], [1], [1]])
result = np.dot(a, b)  # 结果为 [[36]]

矩阵乘法的维度规则

进行矩阵乘法时,必须遵守特定的维度规则:第一个矩阵的列数必须等于第二个矩阵的行数

如果数组 a 的形状是 (m, n),数组 b 的形状是 (p, q),那么只有当 n == p 时,np.dot(a, b) 才是有效的。结果矩阵的形状将是 (m, q)

三维数组的矩阵乘法

np.dot() 函数的功能非常强大,它同样适用于更高维度的数组。对于三维数组,np.dot() 会按照特定的规则在最后一个轴和倒数第二个轴上进行求和计算。

以下是如何创建三维数组并进行矩阵乘法的示例:

import numpy as np
# 创建一个三维数组 x
x = np.array([[[3, 1, 2],
               [5, 2, 3],
               [4, 1, 1]],
              [[3, 2, 4],
               [2, 1, 5],
               [3, 5, 4]]])
# 创建一个与 x 形状相同的全1数组 y
y = np.ones_like(x)
# 进行三维矩阵乘法
result_3d = np.dot(x, y)

np.dot(x, y) 会自动处理三维情况下的复杂计算,这是NumPy库高效和强大的体现。

NumPy数组与矩阵对象

在NumPy中,除了数组(ndarray),还有一种专门的矩阵(matrix) 对象。矩阵对象的一个关键特性是:* 运算符被重载为执行矩阵乘法,而不是逐元素乘法。

以下是创建矩阵对象并使用 * 进行矩阵乘法的示例:

import numpy as np
a = np.array([[11, 12, 13]])
b = np.array([[1], [1], [1]])
# 将数组转换为矩阵对象
ma = np.matrix(a)
mb = np.matrix(b)
# 在矩阵对象中,* 执行矩阵乘法
result_matrix = ma * mb  # 结果为 matrix([[36]])

需要注意的是,虽然矩阵对象在某些线性代数运算上更直观,但NumPy官方文档建议优先使用通用的 ndarray 数组,并使用 np.dot()np.matmul()@ 运算符来进行矩阵乘法,因为数组的应用范围更广,功能也更全面。

核心要点总结

本节课中我们一起学习了NumPy中的矩阵乘法:

  1. 概念区分:矩阵乘法(np.dot())与逐元素乘法(*)是两种完全不同的运算。
  2. 计算规则:矩阵乘法遵循“行乘列”的规则,结果元素是点积之和。
  3. 维度要求:进行矩阵乘法的两个数组必须满足特定的形状匹配条件。
  4. 高维扩展np.dot() 函数可以应用于二维及更高维度的数组。
  5. 对象差异:NumPy的矩阵(matrix)对象将 * 定义为矩阵乘法,而数组(ndarray)对象则需使用特定函数或运算符。

理解并掌握矩阵乘法是进行数据分析、机器学习以及任何涉及线性代数运算的基础。

047:比较与逻辑运算符 🔍

在本节课中,我们将学习NumPy中的比较运算符和逻辑运算符。这些运算符允许我们对数组进行元素级别的比较和逻辑运算,是数据筛选和条件判断的基础。


比较运算符

在Python中,我们经常使用比较运算符。NumPy也支持这些运算符,并且可以对数组进行元素级别的比较。

首先,我们需要导入NumPy库。

import numpy as np

接下来,我们创建两个数组 ab 来进行比较。

a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
b = np.array([1, 5, 3, 5, 5, 2, 4, 9, 9])

现在,我们可以使用 == 运算符来比较这两个数组的对应元素是否相等。

print(a == b)

执行上述代码会返回一个布尔值数组,其中 True 表示对应位置的元素相等,False 表示不相等。这个结果数组的数据类型是布尔型。

除了直接使用运算符,NumPy还提供了一个方法 np.array_equal() 来检查两个数组是否完全相等。

print(np.array_equal(a, b))

如果两个数组的所有元素都相同,则返回 True,否则返回 False


逻辑运算符

上一节我们介绍了比较运算符,本节中我们来看看逻辑运算符。逻辑运算符只能用于布尔数组。

首先,我们定义两个布尔数组。

a_bool = np.array([1, 1, 0, 0], dtype=bool)  # 对应 True, True, False, False
b_bool = np.array([1, 0, 1, 0], dtype=bool)  # 对应 True, False, True, False

以下是NumPy中可用的逻辑运算符函数:

  • np.logical_and:逻辑与。当两个对应元素都为 True 时,结果为 True
    print(np.logical_and(a_bool, b_bool))
    

  • np.logical_or:逻辑或。当两个对应元素中至少有一个为 True 时,结果为 True
    print(np.logical_or(a_bool, b_bool))
    

  • np.logical_xor:逻辑异或。当两个对应元素的值不同时,结果为 True
    print(np.logical_xor(a_bool, b_bool))
    

  • np.logical_not:逻辑非。对数组中的每个布尔值取反。
    print(np.logical_not(a_bool))
    


总结

本节课中我们一起学习了NumPy中的比较运算符和逻辑运算符。我们掌握了如何使用 == 等运算符进行元素级别的数组比较,以及如何使用 np.logical_andnp.logical_ornp.logical_xornp.logical_not 函数对布尔数组进行逻辑运算。这些操作是进行数据筛选、条件判断和构建复杂数据操作逻辑的基础。在后续课程中,我们还将学习如何将这些运算符应用于不同形状的数组。

posted @ 2026-03-26 08:20  布客飞龙II  阅读(0)  评论(0)    收藏  举报