测试3
一、面向对象编程
面向对象基础
1、什么是面向对象编程
面向对象编程是一种基于“对象”的编程范式。它将程序中的数据(属性)和对数据的操作(方法)“捆绑”在一起,形成一个独立的实体——对象。程序被看作是多个对象的集合,通过对象之间的交互来完成复杂的任务。
可以把它想象成现实世界。比如,一辆“汽车”是一个对象,它有属性(颜色、品牌、速度)和方法(加速、刹车、鸣笛)。你不需要知道发动机内部如何工作(内部实现),只需要知道如何操作方向盘和踏板(公开的接口)就能开车。
2、面向对象的四大核心特
1. 封装
概念:将数据(属性)和操作数据的方法捆绑在一起,并隐藏对象的内部实现细节,只对外暴露有限的接口。
目的:
- 安全性:防止外部代码直接意外修改内部数据,只能通过预定义的方法来访问和修改,从而可以对输入进行验证。
- 简化性:使用者无需关心内部复杂逻辑,只需知道如何调用接口即可。
例子:
一个BankAccount对象有一个私有属性balance(余额)。外部不能直接account.balance = 1000000来随意修改金额。必须通过公开的deposit(amount)(存款)和withdraw(amount)(取款)方法,在这些方法内部会进行合法性检查(如取款金额不能大于余额)。
2. 继承
概念:允许一个类(子类/派生类)基于另一个类(父类/基类)来创建。子类会自动获得父类的属性和方法,并可以添加自己特有的属性和方法。
目的:
- 代码复用:避免重复编写相同的代码。公共的部分写在父类,子类只需关注其特有的部分。
- 建立类之间的关系:形成一种层次体系,更符合现实世界的“是一种”关系。
例子:
有一个父类Animal(动物),它有属性name和方法eat()。
我们可以创建子类Dog(狗)和Cat(猫)。它们自动拥有name和eat()方法,同时可以扩展自己的方法,如Dog有bark()(吠叫),Cat有meow()(喵喵叫)。
3. 多态
概念:意为“多种形态”。指同一个方法或操作在不同的对象上可以有不同的行为。
目的:
- 接口统一:允许使用父类的接口来操作子类对象,而具体执行哪个行为由对象自身的实际类型决定。
- 提高灵活性:增加新的子类时,无需修改基于父类编写的通用代码。
例子:
父类Animal有一个方法makeSound()。
子类Dog重写了该方法,实现为“汪汪汪”;子类Cat也重写了该方法,实现为“喵喵喵”。
现在,我们有一个Animal类型的变量,它既可以指向一个Dog对象,也可以指向一个Cat对象。当我们调用这个变量的makeSound()方法时,它会根据实际指向的对象类型来发出不同的叫声。
4. 抽象
概念:只向外界暴露必要的属性和方法,而隐藏不必要的实现细节。抽象可以通过抽象类和接口来实现。
- 抽象类:不能实例化,只能被继承。它可以包含抽象方法(只有声明,没有实现),要求子类必须实现这些方法。
- 接口:一种完全抽象的规范,只定义方法签名,不包含任何实现。类可以“实现”一个或多个接口,并承诺实现接口中定义的所有方法。
目的:
- 定义规范:制定一个契约,规定子类或实现类必须提供哪些功能,但不关心具体如何实现。
- 解耦:将“做什么”和“怎么做”分离,使得代码设计更加灵活。
例子:
定义一个接口Shape(形状),它有一个抽象方法calculateArea()(计算面积)。
然后,让类Circle(圆形)和Rectangle(矩形)去实现这个接口。它们都必须实现calculateArea()方法,但各自的实现逻辑完全不同(圆形是πr²,矩形是长x宽)。我们可以编写一个通用函数,它接收一个Shape类型的参数并调用其calculateArea()方法,而不用关心它具体是哪种形状。
3、基本概念和术语
- 类:是创建对象的“蓝图”或“模板”。它定义了对象将拥有的属性和方法。例如,“汽车”图纸就是一个类。
- 对象:是类的一个具体实例。根据“汽车”图纸制造出来的每一辆实实在在的汽车,就是一个对象。
- 属性:对象的特征或状态(通常是名词),如汽车的颜色、速度。
- 方法:对象的行为或功能(通常是动词),如汽车的加速、刹车。
简单比喻:
类 = 月饼模具
对象 = 用这个模具压出来的每一个月饼
属性 = 月饼的馅料(豆沙、五仁)、形状
方法 = 月饼可以被吃、被包装
4、面向对象 vs. 面向过程
| 特性 | 面向过程编程 | 面向对象编程 |
|---|---|---|
| 核心思想 | 关注解决问题的步骤(函数) | 关注参与解决问题的对象 |
| 程序组织 | 一系列的函数调用 | 一系列对象之间的交互 |
| 数据与函数 | 数据与函数是分离的 | 数据与函数通过封装绑定在一起 |
| 核心概念 | 函数、变量、流程控制 | 类、对象、方法、属性 |
| 特点 | 直观,适合简单任务 | 易维护、易扩展、易复用,适合复杂系统 |
5、优缺点
优点:
- 易维护:代码结构清晰,易于理解和修改。
- 可复用:通过继承和组合,可以高度复用已有的代码。
- 可扩展:通过继承和多态,可以轻松地增加新功能。
- 模型化:更符合人类对现实世界的认知,对大型复杂项目建模能力强。
缺点:
- 性能开销:相比于面向过程编程,由于其更高的抽象程度,通常会带来一些性能损失。
- 复杂度:对于简单问题,设计类和行为可能会显得“杀鸡用牛刀”,更繁琐。
- 设计难度:设计良好的、灵活的类层次结构需要经验和技巧,设计不当会导致代码难以维护。
6、一个简单的代码示例
以下是一个用Python编写的简单例子,展示了上述概念:
# 1. 定义一个类 (封装)
class Dog:
# 初始化方法(构造对象)
def __init__(self, name, breed):
self.name = name # 属性
self.breed = breed # 属性
# 方法
def bark(self):
return "Woof!"
def get_info(self):
return f"{self.name} is a {self.breed}."
# 2. 继承
class GuardDog(Dog): # GuardDog 继承自 Dog
def __init__(self, name, breed, strength):
super().__init__(name, breed) # 调用父类的初始化方法
self.strength = strength # 子类特有的属性
# 3. 多态 (重写父类的方法)
def bark(self):
return "Loud Woof! Intruder alert!"
# 子类特有的方法
def patrol(self):
return f"{self.name} is on patrol."
# 创建对象
my_pet = Dog("Fido", "Golden Retriever")
my_guard_dog = GuardDog("Rex", "German Shepherd", 90)
# 使用对象的方法
print(my_pet.get_info()) # 输出: Fido is a Golden Retriever.
print(my_pet.bark()) # 输出: Woof!
print(my_guard_dog.get_info()) # 输出: Rex is a German Shepherd. (继承的方法)
print(my_guard_dog.bark()) # 输出: Loud Woof! Intruder alert! (重写的方法)
print(my_guard_dog.patrol()) # 输出: Rex is on patrol. (特有的方法)
# 4. 多态的威力:一个函数可以处理不同类型的对象
def hear_dog_bark(dog):
print(f"You hear: {dog.bark()}")
hear_dog_bark(my_pet) # 输出: You hear: Woof!
hear_dog_bark(my_guard_dog) # 输出: You hear: Loud Woof! Intruder alert!7、总结
面向对象编程通过对象来组织代码,其四大支柱封装、继承、多态、抽象共同协作,构建出灵活、健壮且易于维护的软件系统。它是现代软件开发中最为重要的范式之一,被广泛应用于Java、C++、C#、Python、JavaScript等主流编程语言中。
类和对象
1、类
类是一个抽象的概念,它是创建对象的模板或蓝图。类本身并不具体存在,它只定义了一类事物应该具有的属性(特征)和方法(行为)。
- 属性:用变量来表示,用于描述特征(通常是名词)。例如:人的年龄、名字;汽车的品牌、颜色。
- 方法:用函数来表示,用于描述行为(通常是动词)。例如:人的吃饭、走路;汽车的加速、刹车。
类的定义就是回答一个问题:这类东西是什么?有什么特征?能做什么?
举个例子(类的蓝图):
汽车这个类可以这样定义:
- 属性:品牌、颜色、速度、油耗
- 方法:加速()、刹车()、鸣笛()
这就像一张“汽车设计图纸”,它规定了所有汽车都应该有这些属性和功能,但它本身并不是一辆能跑的车。
2、对象
对象是类的一个具体实例。它是根据类的蓝图创建出来的、实实在在存在的一个实体,拥有类中定义的属性和方法,并且每个对象的属性值都可以不同。
对象的创建就是根据蓝图制造出一个具体的东西。
举个例子(对象-实例):
根据汽车这个类,我们可以制造出多个具体的对象:
- 对象1:一辆红色的特斯拉 Model 3,当前速度是 80km/h。
- 对象2:一辆黑色的奥迪 A6,当前速度是 0km/h。
它们都遵循“汽车”类的设计(都有颜色、品牌、速度,都能加速和刹车),但它们是不同的、独立的实体。
3、类和对象的关系
- 抽象 vs. 具体:类是抽象的模板,对象是具体的实例。
- 定义 vs. 实现:类定义了有什么(结构和能力),对象实现了是什么(具体的数值和行为)。
- 一对一 vs. 一对多:一个类可以创建出无数个对象。
核心关系:必须先有类,才能根据类创建出对象。
4、一个生动的比喻:月饼模具和月饼
- 类 = 月饼模具
- 模具定义了月饼的形状(如圆形)、花纹(如“福”字)。这相当于类的属性。
- 模具也定义了月饼的制作方法(倒入原料、压模、脱模)。这相当于类的方法。
- 对象 = 一个个具体的月饼
- 用同一个模具可以压出无数个月饼。
- 每个月饼都有模具规定的形状和花纹(属性),但它们的具体馅料(豆沙、五仁)和被吃的行为可以不同(属性值)。
模具(类)是标准,月饼(对象)是产品。
5、python一切皆对象
“Python 一切皆对象” 是 Python 语言最核心、最重要的设计哲学之一。理解了这句话,就理解了 Python 的“道”。
这不仅意味着你定义的类、函数、模块是对象,甚至一个数字、一个字符串、代码本身,在 Python 中都是以对象的形式存在的
1. 什么是“对象”?
在面向对象编程(OOP)中,对象通常包含两个东西:
- 属性(Attributes):对象所拥有的数据(变量)。
- 方法(Methods):对象可以执行的操作(函数)。
在 Python 中,由于“一切皆对象”,这意味着所有东西都可以拥有属性和方法。
2. 证据:无处不在的对象
让我们用代码来验证这一点。
代码:
- 数字是对象:
# 定义一个整数
x = 5
# 作为对象,它拥有方法(例如 bit_length,获取表示该数字所需的位数)
bit_length = x.bit_length()
print(bit_length) # 输出:3 (因为 5 的二进制 '101' 需要 3 位)
# 它也有一个身份(在内存中的地址)和一个类型
print(id(x)) # 输出:一个唯一的数字(内存地址)
print(type(x)) # 输出:<class 'int'>- 字符串是对象
# 定义一个字符串
s = "hello"
# 调用它的方法,比如转换为大写(会返回一个新的字符串对象)
upper_str = s.upper()
print(upper_str) # 输出:'HELLO'
# 查看它的属性 __doc__,它其实是对象的一个特殊属性,用于存储文档字符串
# print(str.__doc__) 这会打印出字符串类的很长的一段文档说明- 函数是对象
# 定义一个函数
def greet(name):
return f"Hello, {name}!"
# 函数对象也有属性和方法
# 查看函数的名字
print(greet.__name__) # 输出:'greet'
# 甚至可以将函数赋值给另一个变量,就像对待普通对象一样
say_hello = greet
print(say_hello("Alice")) # 输出:'Hello, Alice!'- 模块、类本身也是对象
import math
# 模块 `math` 是一个对象,它有属性(如 pi)
print(math.pi) # 输出:3.141592653589793
# 类 `str` 本身也是一个对象,它是 type 类的一个实例
print(type(str)) # 输出:<class 'type'>“Python 一切皆对象” 不仅仅是一句口号,它是深入 Python 骨髓的设计理念。它意味着在 Python 的世界里,数据和代码的界限变得模糊,所有元素都被一视同仁地作为对象来处理,共享同一套核心机制(属性引用、实例化等)。
6、代码演示
用Python代码来演示如何定义类和创建对象。
# 1. 定义一个类 (使用关键字 `class`)
class Dog:
# 类的属性(直接定义在类中的变量,所有对象共享,不常用)
species = "Canis familiaris"
# 初始化方法 (Constructor)
# 当根据这个类创建新对象时,__init__方法会自动被调用
# `self` 代表对象实例本身,必须是第一个参数
def __init__(self, name, age):
# 实例属性 (使用 self. 来定义)
# 这些属性属于每个具体的对象
self.name = name # 将传入的name参数赋值给对象的name属性
self.age = age # 将传入的age参数赋值给对象的age属性
# 实例方法 (第一个参数必须是self)
def bark(self):
return f"{self.name} says: Woof!"
def get_info(self):
return f"{self.name} is {self.age} years old."
# 2. 创建对象 (实例化一个类)
# 语法:对象名 = 类名(参数...)
# 这里的参数会传递给 __init__ 方法(self不需要我们传)
my_dog = Dog("Fido", 3) # 创建第一个Dog对象
your_dog = Dog("Rex", 5) # 创建第二个Dog对象
# 3. 访问对象的属性和方法
# 访问属性:对象名.属性名
print(my_dog.name) # 输出: Fido
print(your_dog.age) # 输出: 5
# 调用方法:对象名.方法名()
print(my_dog.bark()) # 输出: Fido says: Woof!
print(your_dog.get_info()) # 输出: Rex is 5 years old.
# 4. 每个对象都是独立的
# 修改一个对象的属性,不会影响另一个对象
my_dog.age = 4
print(my_dog.get_info()) # 输出: Fido is 4 years old.
print(your_dog.get_info()) # 输出: Rex is 5 years old. (未被改变)
# 5. 检查对象是否属于某个类
print(isinstance(my_dog, Dog)) # 输出: True
print(type(my_dog)) # 输出: <class '__main__.Dog'>7、总结
| 特征 | 类 | 对象 |
|---|---|---|
| 本质 | 抽象模板、蓝图、定义 | 具体实例、实体、实现 |
| 存在形式 | 逻辑上存在,代码段 | 物理上存在(内存中),运行时创建 |
| 声明方式 | 在程序中用 class 关键字定义一次 |
在程序中可以根据类创建无数次 |
| 内存分配 | 定义类时不会分配内存 | 创建对象时才会在内存中为其分配空间 |
理解类和对象的关系是踏入面向对象编程大门的第一步。所有的后续概念(封装、继承、多态)都是建立在这个基础之上的。
继承和组合
1、继承
继承允许一个类(称为子类或派生类)基于另一个类(称为父类、基类或超类)来创建。子类会自动获得父类的属性和方法,并可以添加自己特有的属性和方法,或者重写父类的方法以改变其行为。
1. 语法示例 (Python)
# 父类 (基类)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement this method")
# 子类 (派生类) - 继承自 Animal
class Dog(Animal): # (Animal) 表示继承
def speak(self): # 重写父类的方法
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# 使用
my_dog = Dog("Rex")
my_cat = Cat("Whiskers")
print(my_dog.speak()) # 输出: Rex says Woof!
print(my_cat.speak()) # 输出: Whiskers says Meow!2. 优点
- 代码复用:子类可以直接复用父类的代码,无需重写。
- 易于扩展:可以轻松创建新的子类来添加功能。
- 易于理解:建立了一个清晰的层次结构(例如,
Dog是一种Animal),非常符合直觉。
3. 缺点
- 强耦合:子类与父类紧密绑定。父类的任何改变(尤其是接口改变)都可能破坏子类的功能。
- 层次结构可能变得僵化:在设计初期建立的继承层次,随着需求变化可能变得不再合理,修改起来会很困难。
- 可能破坏封装:子类可以访问父类的受保护成员,这意味着子类的实现依赖于父类的实现细节。
4. 使用原则 (“Is-A”测试)
在决定使用继承前,问自己:“子类是否是父类的一种更具体类型?” 或者说 “B 是一种 A 吗?”
Dog是一种Animal-> 适合继承。Car是一种Vehicle-> 适合继承。(注:Vehicle 运载工具)Engine是一种Car? -> 不适合! 应该用组合。
2、组合
组合是一种设计 (technique),一个类(通常称为复合类)将另一个类(或几个类)的对象作为自己的成员变量(或部件)来使用。通过调用成员对象的方法来获得功能,从而建立“拥有”关系。
1. 语法示例 (Python)
# 部件类
class Engine:
def start(self):
return "Engine started! Vroom vroom..."
class Wheel:
def __init__(self, position):
self.position = position
def rotate(self):
return f"{self.position} wheel is rotating."
# 复合类 - 使用组合
class Car:
def __init__(self):
# 将其他类的实例作为自己的属性 -> 组合
self.engine = Engine()
self.wheels = [Wheel("Front left"), Wheel("Front right"),
Wheel("Rear left"), Wheel("Rear right")]
def drive(self):
# 通过调用部件对象的方法来实现功能
result = [self.engine.start()]
for wheel in self.wheels:
result.append(wheel.rotate())
return result
# 使用
my_car = Car()
actions = my_car.drive()
for action in actions:
print(action)输出:
Engine started! Vroom vroom...
Front left wheel is rotating.
Front right wheel is rotating.
Rear left wheel is rotating.
Rear right wheel is rotating.2. 优点
- 松耦合:
Car类只依赖于Engine和Wheel的公共接口,而不依赖于它们的内部实现。可以轻松更换不同的引擎或轮子。 - 高灵活性:可以在运行时动态地改变对象的行为(例如,给车换一个更强大的引擎)。
- 更好的封装:每个类都专注于自己的功能,类之间通过清晰的接口交互。
- 可复用性:
Engine和Wheel是独立的,可以很容易地被其他类(如Truck,Motorcycle)复用。
3. 缺点
- 系统复杂性:需要管理的类对象更多。
- 需要显式委托:为了使用部件的功能,必须在复合类中编写代码来调用部件的方法,不像继承那样自动拥有方法。
4. 使用原则 (“Has-A”测试)
在决定使用组合前,问自己:“一个类是否拥有另一个类的对象作为其一部分?”
Car有一个Engine-> 适合组合。Computer有一个CPU-> 适合组合。Bird有一个Wing-> 适合组合。
3、如何选择:继承 vs. 组合?
这是一个经典的设计决策问题。遵循以下原则和现代设计理念:
- 优先使用组合而非继承:这是最重要的设计原则之一。组合提供了更大的灵活性、更低的耦合度,并且让代码更容易测试和维护。
- “Is-A”用继承,“Has-A”用组合:
- 如果你想表达的是分类关系(
Dog是一种Animal),并且子类确实是父类的特殊版本,使用继承。 - 如果你想表达的是整体与部分的关系(
Car有一个Engine),或者只是想复用另一个类的功能,使用组合。 - 考虑多态性:如果你需要让一组不同的对象对同一消息做出不同的反应(多态),并且这些对象共享一个共同的基类接口,那么继承层次结构是非常合适的。组合也可以通过接口来实现多态。
- 考虑关系的稳定性:继承关系在编译时确定,是静态的,通常更稳定。组合关系是动态的,可以在运行时改变,更适合可能变化的需求。
4、总结与类比
| 场景 | 继承 (Is-A) | 组合 (Has-A) |
|---|---|---|
| 关系 | 分类、特化 | 组装、包含 |
| 代码例子 | class Manager(Employee): |
class Car: self.engine = Engine() |
| 现实例子 | 特斯拉Model 3 是一辆 电动汽车 | 一辆汽车 有一个 发动机和四个轮子 |
记住这条黄金法则:除非你非常确定两个类之间是严格的“是一个”关系,否则都应该首选组合。 组合能让你构建出更健壮、更灵活、更易于演进的系统。
多态和封装
多态
多态(Polymorphism)在Python中依然遵循“同一接口,多种实现”的核心思想,即不同类型的对象可以通过相同的方法名调用,表现出不同的行为。与静态类型语言(如Java)不同,Python作为动态类型语言,其多态实现更灵活——不依赖严格的类型检查,而是通过“对象是否具备所需方法/属性”来判断能否执行操作,这种特性被称为“鸭子类型”(Duck Typing):“如果一个对象走起来像鸭子、叫起来像鸭子,那么它就是鸭子”。
1. 概念解析
多态是指同一操作作用于不同的对象可以有不同的解释,产生不同的执行结果。在Python中,多态主要通过以下方式实现:
- 继承和方法重写
- 鸭子类型(Duck Typing)
- 抽象基类(ABC)
2. 多态的类型
2.1 继承多态
通过继承和方法重写实现多态:
class Animal:
def speak(self):
raise NotImplementedError("子类必须实现此方法")
class Dog(Animal):
def speak(self):
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵!"
class Duck(Animal):
def speak(self):
return "嘎嘎!"
def animal_speak(animal):
print(animal.speak())
# 使用多态
animals = [Dog(), Cat(), Duck()]
for animal in animals:
animal_speak(animal) # 同一接口,不同表现2.2 鸭子类型
Python特色的多态实现方式:"如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子"
class Dog:
def speak(self):
return "汪汪!"
class Robot:
def speak(self):
return "我是机器人!"
class Person:
def speak(self):
return "你好!"
def make_it_speak(thing):
print(thing.speak())
# 所有有speak方法的对象都可以使用
make_it_speak(Dog())
make_it_speak(Robot())
make_it_speak(Person())2.3 抽象基类(ABC)
使用抽象基类强制实现多态接口:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
def perimeter(self):
return 2 * 3.14 * self.radius
# 使用多态处理不同图形
shapes = [Rectangle(5, 10), Circle(7)]
for shape in shapes:
print(f"面积: {shape.area()}, 周长: {shape.perimeter()}")3. 运算符重载
Python允许通过特殊方法重载运算符,这也是多态的一种形式:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""重载+运算符"""
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""重载-运算符"""
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""重载*运算符"""
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
# 使用运算符重载
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # Vector(6, 8)
print(v2 - v1) # Vector(2, 2)
print(v1 * 3) # Vector(6, 9)封装
1. 概念解析
封装是面向对象编程的核心概念之一,它将数据和对数据的操作封装在一起,形成一个独立的单元(类)。通过访问控制机制,封装隐藏了对象的内部实现细节,只暴露必要的接口与外界交互。
2. 封装的目的
- 数据隐藏:保护对象内部状态,防止外部直接访问和修改
- 接口统一:提供统一的访问方式,简化使用
- 实现隔离:内部实现变化不影响外部使用
- 提高安全性:通过验证机制保护数据完整性
3. Python中的封装实现
3.1 访问控制
Python使用命名约定来实现访问控制:
- 公共成员:普通命名(如:
name) - 受保护成员:单下划线前缀(如:
_name) - 约定上的保护,实际仍可访问 - 私有成员:双下划线前缀(如:
__name) - 名称修饰(name mangling)实现伪私有
3.2 属性封装
使用属性装饰器(@property)实现更精细的封装控制:
class Person:
def __init__(self, name, age):
self._name = name # 受保护属性
self.__age = age # 私有属性
# 属性获取器
@property
def name(self):
return self._name
# 属性设置器
@name.setter
def name(self, value):
if not value:
raise ValueError("姓名不能为空")
self._name = value
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if value < 0 or value > 150:
raise ValueError("年龄必须在0-150之间")
self.__age = value
# 使用示例
p = Person("张三", 25)
print(p.name) # 通过属性访问
p.name = "李四" # 通过设置器修改
# p.age = 200 # 会抛出ValueError异常4. 封装案例:银行账户
class BankAccount:
def __init__(self, account_holder, initial_balance=0):
self._account_holder = account_holder # 受保护属性
self.__balance = initial_balance # 私有属性
self.__transaction_history = [] # 私有属性
def deposit(self, amount):
if amount <= 0:
raise ValueError("存款金额必须大于0")
self.__balance += amount
self.__transaction_history.append(f"存款: +{amount}")
return self.__balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("取款金额必须大于0")
if amount > self.__balance:
raise ValueError("余额不足")
self.__balance -= amount
self.__transaction_history.append(f"取款: -{amount}")
return self.__balance
@property
def balance(self):
return self.__balance
@property
def account_holder(self):
return self._account_holder
def get_transaction_history(self):
return self.__transaction_history.copy() # 返回副本,保护原始数据
# 使用示例
account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(200)
print(f"余额: {account.balance}")
print(f"交易记录: {account.get_transaction_history()}")
# account.__balance = 10000 # 无法直接修改私有属性总结对比
| 特性 | 封装 | 多态 |
|---|---|---|
| 目的 | 隐藏实现细节,保护数据安全 | 统一接口,多样化实现 |
| 实现方式 | 访问控制、属性装饰器 | 继承重写、鸭子类型、抽象基类 |
| 优势 | 提高代码安全性、可维护性 | 提高代码灵活性、可扩展性 |
| Python特色 | 名称修饰、属性装饰器 | 鸭子类型、运算符重载 |
最佳实践
- 合理使用封装:
- 将变化频繁的实现细节封装起来
- 通过属性访问器控制对关键数据的访问
- 使用私有成员保护内部状态
- 充分利用多态:
- 基于接口而非实现编程
- 利用鸭子类型减少类之间的耦合
- 适当使用抽象基类定义接口规范
- 结合使用:
- 封装提供稳定的内部实现
- 多态提供灵活的外部接口
- 二者结合创建健壮且易扩展的系统
常用的魔法方法
魔法方法(Magic Methods)是 Python 中特殊的方法,它们以双下划线开头和结尾(如 __init__)。这些方法允许我们自定义类的行为,使其与 Python 的内置函数和操作符协同工作。
一、构造与销毁方法
1. __new__(cls, [...])
作用:创建类的新实例,在 __init__ 之前调用
使用场景:控制对象创建过程,实现单例模式等
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
# 测试
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True - 单例模式2. __init__(self, [...])
作用:对象初始化方法
使用场景:设置对象的初始状态
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
self.created_at = datetime.now()
p = Person("Alice", 25)3. __del__(self)
作用:对象销毁方法(析构函数)
使用场景:清理资源,如关闭文件、数据库连接等
class FileHandler:
def __init__(self, filename):
self.file = open(filename, 'r')
def __del__(self):
if hasattr(self, 'file') and self.file:
self.file.close()
print("文件已关闭")
# 当对象被垃圾回收时自动调用二、表示与转换方法
4. __str__(self)
作用:定义对象的字符串表示(用户友好)
使用场景:用于 print() 和 str() 函数
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p) # 输出: Point(3, 4)5. __repr__(self)
作用:定义对象的官方字符串表示(开发者友好)
使用场景:用于 repr() 函数,通常可以用于重新创建对象
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(repr(p)) # 输出: Point(3, 4)6. __format__(self, format_spec)
作用:定义对象在格式化字符串中的行为
使用场景:自定义格式化输出
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __format__(self, format_spec):
if format_spec == 'f':
return f"{self.celsius * 9/5 + 32}°F"
else:
return f"{self.celsius}°C"
temp = Temperature(25)
print(f"{temp}") # 25°C
print(f"{temp:f}") # 77.0°F三、比较运算符方法
7. __eq__(self, other)
作用:定义等于操作符 == 的行为
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __eq__(self, other):
if not isinstance(other, Book):
return False
return self.title == other.title and self.author == other.author
book1 = Book("Python", "Guido")
book2 = Book("Python", "Guido")
print(book1 == book2) # True8. __lt__(self, other), __le__(self, other), __gt__(self, other), __ge__(self, other)
作用:定义比较操作符 <, <=, >, >= 的行为
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __lt__(self, other):
return self.score < other.score
def __eq__(self, other):
return self.score == other.score
students = [
Student("Alice", 85),
Student("Bob", 92),
Student("Charlie", 78)
]
# 排序会使用 __lt__ 方法
sorted_students = sorted(students)
for student in sorted_students:
print(f"{student.name}: {student.score}")9. __hash__(self)
作用:定义对象的哈希值
使用场景:使对象可作为字典键或集合元素
class Employee:
def __init__(self, id, name):
self.id = id
self.name = name
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return hash(self.id)
# 现在 Employee 对象可以作为字典键
employees = {
Employee(1, "Alice"): "Manager",
Employee(2, "Bob"): "Developer"
}四、数学运算符方法
10. __add__(self, other), __sub__(self, other), __mul__(self, other), 等
作用:定义数学运算符的行为
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # Vector(6, 8)
print(v1 * 3) # Vector(6, 9)11. __radd__(self, other), __rsub__(self, other), 等
作用:定义反向数学运算符的行为
使用场景:当左操作数不支持操作时调用
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar):
return self.__mul__(scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v = Vector(2, 3)
print(3 * v) # 使用 __rmul__: Vector(6, 9)五、容器类型方法
12. __len__(self)
作用:定义 len() 函数的行为
class Playlist:
def __init__(self, songs):
self.songs = songs
def __len__(self):
return len(self.songs)
playlist = Playlist(["Song1", "Song2", "Song3"])
print(len(playlist)) # 313. __getitem__(self, key)
作用:定义索引操作 self[key] 的行为
class Playlist:
def __init__(self, songs):
self.songs = songs
def __getitem__(self, index):
return self.songs[index]
def __len__(self):
return len(self.songs)
playlist = Playlist(["Song1", "Song2", "Song3"])
print(playlist[1]) # Song214. __setitem__(self, key, value)
作用:定义赋值操作 self[key] = value 的行为
class Playlist:
def __init__(self, songs):
self.songs = songs
def __setitem__(self, index, value):
self.songs[index] = value
playlist = Playlist(["Song1", "Song2", "Song3"])
playlist[1] = "New Song"
print(playlist.songs) # ['Song1', 'New Song', 'Song3']15. __contains__(self, item)
作用:定义 in 操作符的行为
class Playlist:
def __init__(self, songs):
self.songs = songs
def __contains__(self, item):
return item in self.songs
playlist = Playlist(["Song1", "Song2", "Song3"])
print("Song2" in playlist) # True六、可调用对象方法
16. __call__(self, [...])
作用:使实例可以像函数一样被调用
class Adder:
def __init__(self, base):
self.base = base
def __call__(self, x):
return self.base + x
add5 = Adder(5)
print(add5(3)) # 8七、上下文管理方法
17. __enter__(self) 和 __exit__(self, exc_type, exc_val, exc_tb)
作用:定义 with 语句的行为
class FileHandler:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# 使用 with 语句自动管理资源
with FileHandler("test.txt", "w") as f:
f.write("Hello, World!")八、属性访问方法
18. __getattr__(self, name)
作用:当访问不存在的属性时调用
class DynamicAttributes:
def __getattr__(self, name):
if name == "color":
return "blue"
elif name == "size":
return 10
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
obj = DynamicAttributes()
print(obj.color) # blue
print(obj.size) # 1019. __setattr__(self, name, value)
作用:当设置属性时调用
class ValidatedAttribute:
def __setattr__(self, name, value):
if name == "age" and (value < 0 or value > 150):
raise ValueError("年龄必须在0-150之间")
super().__setattr__(name, value)
person = ValidatedAttribute()
person.age = 25 # 正常
# person.age = 200 # 抛出 ValueError20. __getattribute__(self, name)
作用:当访问任何属性时调用(包括存在的属性)
class LoggingAttributes:
def __getattribute__(self, name):
print(f"访问属性: {name}")
return super().__getattribute__(name)
obj = LoggingAttributes()
obj.x = 10
print(obj.x) # 先打印"访问属性: x",然后输出10九、描述符方法
21. __get__(self, instance, owner), __set__(self, instance, value), __delete__(self, instance)
作用:定义描述符协议,用于创建托管属性
class PositiveNumber:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value <= 0:
raise ValueError("必须是正数")
instance.__dict__[self.name] = value
class Circle:
radius = PositiveNumber('radius')
def __init__(self, radius):
self.radius = radius
c = Circle(5)
print(c.radius) # 5
# c.radius = -1 # 抛出 ValueError总结
魔法方法是 Python 面向对象编程的强大特性,它们允许我们自定义类的行为,使其与 Python 的内置功能无缝集成。掌握这些魔法方法可以帮助你编写更加 Pythonic 的代码,创建更加灵活和强大的类。
在实际开发中,应根据需求选择合适的魔法方法进行实现,避免过度使用导致代码难以理解和维护。合理使用魔法方法可以大大提高代码的可读性和可用性。
二、反射机制
反射(Reflection)是程序在运行时检查、修改自身状态和行为的能力。Python 提供了强大的反射机制,允许开发者在运行时动态地操作对象、类和模块。
一、反射核心函数
1. getattr(object, name[, default])
获取对象的属性或方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Hello, I'm {self.name}"
# 使用 getattr
p = Person("Alice", 25)
name = getattr(p, "name") # 获取属性
method = getattr(p, "greet") # 获取方法
result = method() # 调用方法
print(name) # Alice
print(result) # Hello, I'm Alice
# 使用默认值
nickname = getattr(p, "nickname", "No nickname")
print(nickname) # No nickname2. setattr(object, name, value)
设置对象的属性值
class Config:
pass
config = Config()
# 动态设置属性
setattr(config, "debug", True)
setattr(config, "host", "localhost")
setattr(config, "port", 8080)
print(config.debug) # True
print(config.host) # localhost
print(config.port) # 80803. hasattr(object, name)
检查对象是否有指定属性或方法
class Calculator:
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
calc = Calculator()
# 检查方法是否存在
print(hasattr(calc, "add")) # True
print(hasattr(calc, "subtract")) # False
# 检查属性是否存在
print(hasattr(calc, "result")) # False4. delattr(object, name)
删除对象的属性
class Data:
def __init__(self):
self.value = 42
self.temp = "temporary"
data = Data()
print(hasattr(data, "temp")) # True
delattr(data, "temp")
print(hasattr(data, "temp")) # False二、反射高级应用
1. 动态调用方法
class MathOperations:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def execute_operation(operation, a, b):
math_ops = MathOperations()
# 检查方法是否存在
if not hasattr(math_ops, operation):
raise AttributeError(f"Operation '{operation}' not found")
# 获取方法
method = getattr(math_ops, operation)
# 调用方法
return method(a, b)
# 动态调用
print(execute_operation("add", 5, 3)) # 8
print(execute_operation("multiply", 4, 6)) # 242. 插件系统实现
# plugins/__init__.py
import importlib
import os
class PluginManager:
def __init__(self):
self.plugins = {}
def load_plugins(self, plugin_dir):
# 获取插件目录下的所有Python文件
for filename in os.listdir(plugin_dir):
if filename.endswith(".py") and filename != "__init__.py":
module_name = filename[:-3] # 移除.py扩展名
try:
# 动态导入模块
module = importlib.import_module(f"plugins.{module_name}")
# 检查模块是否有Plugin类
if hasattr(module, "Plugin"):
plugin_class = getattr(module, "Plugin")
plugin_instance = plugin_class()
# 获取插件名称
plugin_name = getattr(plugin_instance, "name", module_name)
# 注册插件
self.plugins[plugin_name] = plugin_instance
print(f"Loaded plugin: {plugin_name}")
except ImportError as e:
print(f"Failed to load plugin {module_name}: {e}")
def execute_plugin(self, plugin_name, *args, **kwargs):
if plugin_name not in self.plugins:
raise ValueError(f"Plugin '{plugin_name}' not found")
plugin = self.plugins[plugin_name]
# 检查插件是否有execute方法
if hasattr(plugin, "execute"):
return plugin.execute(*args, **kwargs)
else:
raise AttributeError(f"Plugin '{plugin_name}' has no execute method")
# 示例插件 plugins/calculator.py
class Plugin:
name = "calculator"
def execute(self, operation, a, b):
operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else "Error: Division by zero"
}
if operation in operations:
return operations[operation](a, b)
else:
return f"Error: Unknown operation '{operation}'"
# 使用插件系统
manager = PluginManager()
manager.load_plugins("plugins")
result = manager.execute_plugin("calculator", "add", 5, 3)
print(result) # 83. 配置系统
import json
class Config:
def __init__(self, config_file):
self.config_file = config_file
self.load_config()
def load_config(self):
try:
with open(self.config_file, 'r') as f:
config_data = json.load(f)
# 动态设置配置属性
for key, value in config_data.items():
setattr(self, key, value)
except FileNotFoundError:
print(f"Config file {self.config_file} not found, using defaults")
self.set_defaults()
def set_defaults(self):
# 设置默认配置
defaults = {
"debug": False,
"host": "localhost",
"port": 8080,
"database": {
"host": "localhost",
"name": "app_db",
"user": "admin"
}
}
for key, value in defaults.items():
setattr(self, key, value)
def save_config(self):
config_data = {}
# 收集所有不以_开头的属性
for key in dir(self):
if not key.startswith('_') and not callable(getattr(self, key)):
value = getattr(self, key)
# 只序列化基本类型、列表和字典
if isinstance(value, (str, int, float, bool, list, dict, type(None))):
config_data[key] = value
with open(self.config_file, 'w') as f:
json.dump(config_data, f, indent=2)
def __getitem__(self, key):
# 支持字典式访问
return getattr(self, key)
def __setitem__(self, key, value):
# 支持字典式设置
setattr(self, key, value)
# 使用配置系统
config = Config("app_config.json")
print(config.host) # 从配置文件读取或使用默认值
# 动态修改配置
config.host = "127.0.0.1"
config["port"] = 9000 # 使用字典语法
# 保存配置
config.save_config()4. REST API 路由映射
class APIController:
def get_users(self):
return ["user1", "user2", "user3"]
def get_user(self, user_id):
return f"User {user_id}"
def create_user(self, user_data):
return f"Created user: {user_data['name']}"
def update_user(self, user_id, user_data):
return f"Updated user {user_id}: {user_data['name']}"
def delete_user(self, user_id):
return f"Deleted user {user_id}"
class Router:
def __init__(self):
self.controller = APIController()
self.routes = {
"GET:/api/users": "get_users",
"GET:/api/users/{id}": "get_user",
"POST:/api/users": "create_user",
"PUT:/api/users/{id}": "update_user",
"DELETE:/api/users/{id}": "delete_user"
}
def handle_request(self, method, path, **kwargs):
# 构建路由键
route_key = f"{method}:{path}"
# 查找匹配的路由
for pattern, handler_name in self.routes.items():
if self._match_route(pattern, route_key):
# 获取处理方法
if hasattr(self.controller, handler_name):
handler = getattr(self.controller, handler_name)
return handler(**kwargs)
return {"error": "Route not found"}, 404
def _match_route(self, pattern, route_key):
# 简单的路由匹配逻辑
if pattern == route_key:
return True
# 处理带参数的路由
if "{" in pattern and "}" in pattern:
pattern_parts = pattern.split("/")
route_parts = route_key.split("/")
if len(pattern_parts) != len(route_parts):
return False
for p_part, r_part in zip(pattern_parts, route_parts):
if p_part.startswith("{") and p_part.endswith("}"):
continue
if p_part != r_part:
return False
return True
return False
# 使用路由系统
router = Router()
# 模拟HTTP请求
response = router.handle_request("GET", "/api/users")
print(response) # ["user1", "user2", "user3"]
response = router.handle_request("GET", "/api/users/123")
print(response) # "User 123"
response = router.handle_request("POST", "/api/users", user_data={"name": "Alice"})
print(response) # "Created user: Alice"5. 动态类创建和修改
def create_class(class_name, base_classes=None, attributes=None):
"""动态创建类"""
if base_classes is None:
base_classes = (object,)
if attributes is None:
attributes = {}
# 动态创建类
new_class = type(class_name, base_classes, attributes)
return new_class
def add_method_to_class(cls, method_name, method):
"""向类添加方法"""
setattr(cls, method_name, method)
# 动态创建类
Person = create_class(
"Person",
attributes={
"__init__": lambda self, name: setattr(self, "name", name),
"greet": lambda self: f"Hello, I'm {self.name}"
}
)
# 使用动态创建的类
p = Person("Bob")
print(p.greet()) # Hello, I'm Bob
# 动态添加方法
def introduce(self):
return f"My name is {self.name}"
add_method_to_class(Person, "introduce", introduce)
print(p.introduce()) # My name is Bob6. 对象序列化和反序列化
import json
class Serializable:
def to_dict(self):
"""将对象转换为字典"""
result = {}
for key in dir(self):
if not key.startswith('_') and not callable(getattr(self, key)):
value = getattr(self, key)
# 只序列化基本类型、列表和字典
if isinstance(value, (str, int, float, bool, list, dict, type(None))):
result[key] = value
return result
def to_json(self):
"""将对象转换为JSON字符串"""
return json.dumps(self.to_dict())
@classmethod
def from_dict(cls, data):
"""从字典创建对象"""
obj = cls()
for key, value in data.items():
setattr(obj, key, value)
return obj
@classmethod
def from_json(cls, json_str):
"""从JSON字符串创建对象"""
data = json.loads(json_str)
return cls.from_dict(data)
class User(Serializable):
def __init__(self, name=None, email=None, age=None):
self.name = name
self.email = email
self.age = age
# 使用序列化功能
user = User("Alice", "alice@example.com", 25)
# 序列化为JSON
json_str = user.to_json()
print(json_str) # {"name": "Alice", "email": "alice@example.com", "age": 25}
# 从JSON反序列化
new_user = User.from_json(json_str)
print(new_user.name) # Alice
print(new_user.email) # alice@example.com
print(new_user.age) # 25三、反射使用场景总结
- 插件系统:动态加载和执行插件
- 配置管理:动态读取和修改配置
- Web框架:路由映射、请求处理
- 测试框架:动态发现和执行测试用例
- ORM系统:动态映射数据库表和对象属性
- 序列化/反序列化:对象与各种格式的相互转换
- 代码生成:动态创建类和函数
- API客户端:动态构建请求和处理响应
四、注意事项
- 性能考虑:反射操作通常比直接调用慢,应避免在性能关键代码中过度使用
- 安全性:动态执行代码可能带来安全风险,特别是当处理用户输入时
- 可读性:过度使用反射可能降低代码的可读性和可维护性
- 错误处理:反射操作可能抛出多种异常,应妥善处理
五、最佳实践
- 适度使用:只在确实需要动态行为时使用反射
- 封装反射逻辑:将反射操作封装在专门的函数或类中
- 添加类型检查:在使用反射获取的属性或方法前进行类型检查
- 提供备用方案:为反射操作提供默认值或备用方案
- 文档化:清晰地文档化使用反射的代码部分
反射是Python强大的特性之一,合理使用可以极大地提高代码的灵活性和可扩展性,但需要谨慎使用以避免复杂性和潜在问题。
三、设计模式
Python 作为一种灵活的动态语言,其实现设计模式的方式往往与静态语言(如 Java/C++)不同。它通常更简洁,有时甚至通过语言特性(如一等函数、元类等)天然地支持了某些模式。
设计模式通常分为三大类:创建型、结构型和行为型。
一、 创建型模式
关注对象创建机制,以增加代码的灵活性和可复用性。
1. 单例模式
- 意图: 确保一个类只有一个实例,并提供一个全局访问点。
- Python 实现: 通常使用模块(天然单例)或元类
__new__方法。 - 场景案例: 数据库连接池、日志记录器、应用的配置对象。
- 代码解析:
# 方法一:使用 __new__ (经典方法)
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
# 方法二:使用元类 (更Pythonic, 控制类的创建过程)
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def __init__(self):
print("建立数据库连接...")
# 假设这里有一些初始化操作
# 测试
db1 = DatabaseConnection() # 输出:建立数据库连接...
db2 = DatabaseConnection()
print(db1 is db2) # 输出:True
# 方法三:最推荐的方法 - 直接使用模块
# 在一个 config.py 文件中
class _Config:
DEBUG = True
DATABASE_URI = 'sqlite:///app.db'
config = _Config() # 在模块导入时,这个实例就被创建了
# 在另一个文件中
# from config import config
# print(config.DEBUG) # 所有地方导入的都是同一个 config 实例2. 工厂方法模式
- 意图: 定义一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- Python 实现: 使用类方法或独立的函数作为工厂。
- 场景案例: 根据文件扩展名创建不同的解析器(
JsonParser,XmlParser);根据用户平台创建不同的UI按钮。 - 代码解析:
from abc import ABC, abstractmethod
# 产品接口
class Parser(ABC):
@abstractmethod
def parse(self, data: str):
pass
# 具体产品
class JsonParser(Parser):
def parse(self, data: str):
print(f"Parsing JSON: {data}")
# ... 具体解析逻辑
class XmlParser(Parser):
def parse(self, data: str):
print(f"Parsing XML: {data}")
# ... 具体解析逻辑
# 创建者(工厂)
class ParserFactory(ABC):
# 工厂方法
@abstractmethod
def create_parser(self) -> Parser:
pass
# 也可以有业务逻辑
def parse_data(self, data: str):
parser = self.create_parser() # 调用工厂方法创建产品
parser.parse(data)
# 具体创建者
class JsonParserFactory(ParserFactory):
def create_parser(self) -> Parser:
return JsonParser()
class XmlParserFactory(ParserFactory):
def create_parser(self) -> Parser:
return XmlParser()
# 使用
def read_file_and_parse(factory: ParserFactory, file_path: str):
with open(file_path, 'r') as f:
data = f.read()
factory.parse_data(data)
# 客户端代码根据文件类型选择工厂
json_factory = JsonParserFactory()
read_file_and_parse(json_factory, 'data.json')
# 更简单的实现:使用函数工厂(Python中非常常见)
def create_parser(format_type: str) -> Parser:
if format_type == 'json':
return JsonParser()
elif format_type == 'xml':
return XmlParser()
else:
raise ValueError(f"Unknown format: {format_type}")
parser = create_parser('json')
parser.parse('{}')3. 抽象工厂模式
- 意图: 提供一个接口,用于创建相关或依赖对象的家族,而不需要指定它们具体的类。
- Python 实现: 创建多个工厂方法。
- 场景案例: 跨平台UI工具包(创建一套Windows风格的按钮/文本框,或者一套Mac风格的按钮/文本框)。
- 代码解析:
from abc import ABC, abstractmethod
# 抽象产品 A
class Button(ABC):
@abstractmethod
def click(self):
pass
# 抽象产品 B
class TextField(ABC):
@abstractmethod
def input(self, text: str):
pass
# 具体产品家族 1: Windows
class WindowsButton(Button):
def click(self):
print("Windows button clicked")
class WindowsTextField(TextField):
def input(self, text: str):
print(f"Windows text field input: {text}")
# 具体产品家族 2: Mac
class MacButton(Button):
def click(self):
print("Mac button clicked")
class MacTextField(TextField):
def input(self, text: str):
print(f"Mac text field input: {text}")
# 抽象工厂
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_text_field(self) -> TextField:
pass
# 具体工厂 1
class WindowsFactory(GUIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_text_field(self) -> TextField:
return WindowsTextField()
# 具体工厂 2
class MacFactory(GUIFactory):
def create_button(self) -> Button:
return MacButton()
def create_text_field(self) -> TextField:
return MacTextField()
# 客户端代码
class Application:
def __init__(self, factory: GUIFactory):
self._factory = factory
self._button = None
self._text_field = None
def create_ui(self):
# 使用同一家族的组件
self._button = self._factory.create_button()
self._text_field = self._factory.create_text_field()
def run(self):
self._button.click()
self._text_field.input("Hello World")
# 根据配置或系统环境选择工厂
config = "windows" # 可以从环境变量或配置文件中读取
if config == "windows":
factory = WindowsFactory()
else:
factory = MacFactory()
app = Application(factory)
app.create_ui()
app.run()二、 结构型模式
关注类和对象的组合,形成更大的结构。
4. 适配器模式
- 意图: 将一个类的接口转换成客户希望的另一个接口。使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- Python 实现: 使用组合,让适配器类持有被适配对象,并实现目标接口。
- 场景案例: 集成第三方库或遗留代码;让不兼容的接口协同工作。
- 代码解析:
# 目标接口 (客户期望的接口)
class USPlug:
def voltage(self):
return 110
def live(self):
return 1
def neutral(self):
return -1
# 被适配者 (已有的,但不兼容的接口)
class EuropeanPlug:
def voltage(self):
return 220
def live(self):
return 1
def earth(self): # !注意,这里不是 neutral, 是 earth
return 0
# 适配器 (让 EuropeanPlug 看起来像 USPlug)
class EuropeanToUSPlugAdapter(USPlug):
def __init__(self, european_plug):
self._european_plug = european_plug # 组合被适配对象
def voltage(self):
# 可能还需要一个变压器,这里简单返回
return self._european_plug.voltage()
def live(self):
return self._european_plug.live()
def neutral(self):
# 欧洲插头没有 neutral, 我们用 earth 来适配
# 这是一种“近似”适配,可能不完全符合电气规范,但说明了模式思想
return self._european_plug.earth()
# 客户端代码,它只知道 USPlug 接口
class AmericanSocket:
def connect(self, plug: USPlug):
if plug.voltage() != 110:
raise ValueError("Voltage mismatch!")
print(f"Connected successfully. Live: {plug.live()}, Neutral: {plug.neutral()}")
# 使用
us_plug = USPlug()
socket = AmericanSocket()
socket.connect(us_plug)
# 现在,我们想接入一个欧洲插头
eu_plug = EuropeanPlug()
# socket.connect(eu_plug) # TypeError,接口不兼容
# 使用适配器
adapter = EuropeanToUSPlugAdapter(eu_plug)
socket.connect(adapter) # 成功连接!5. 装饰器模式
- 意图: 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- Python 实现: Python 有内置的装饰器语法
@,但其本质是函数和类的组合。我们也可以自己实现类装饰器。 - 场景案例: Django 中的
@login_required;为函数添加日志、计时、权限检查等功能;Flask 的路由@app.route。 - 代码解析:
# 1. 函数装饰器 (最常见的用法)
def logger(func):
"""一个记录函数执行的装饰器"""
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} finished")
return result
return wrapper
@logger # 相当于 say_hello = logger(say_hello)
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
# 输出:
# Calling function: say_hello
# Hello, Alice!
# Function say_hello finished
# 2. 带参数的函数装饰器
def repeat(num_times):
"""重复执行函数指定次数的装饰器工厂"""
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet():
print("Hi there!")
greet() # 输出 3 次 "Hi there!"
# 3. 类装饰器 (较少用,但可以保持状态)
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def example():
print("Inside example")
example() # Call 1 of example...
example() # Call 2 of example...6. 外观模式
- 意图: 为子系统中的一组接口提供一个一致的界面。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- Python 实现: 创建一个新类,封装子系统的复杂性。
- 场景案例: 封装复杂的第三方库API;简化复杂的系统调用流程(如电脑开机,只需要按一个按钮,背后却涉及CPU、内存、硬盘等复杂操作)。
- 代码解析:
# 复杂的子系统类
class CPU:
def start(self):
print("CPU is starting up...")
def execute(self):
print("CPU is executing instructions...")
class Memory:
def load(self):
print("Memory is loading data...")
class HardDrive:
def read(self):
print("Hard Drive is reading boot sector...")
# 外观
class ComputerFacade:
def __init__(self):
self.cpu = CPU()
self.memory = Memory()
self.hard_drive = HardDrive()
def start_computer(self):
"""提供一个简单的启动方法,隐藏背后的复杂步骤"""
print("Computer is starting up...")
self.cpu.start()
self.memory.load()
self.hard_drive.read()
self.cpu.execute()
print("Computer is ready!")
# 客户端代码
computer = ComputerFacade()
computer.start_computer()
# 用户不需要知道 CPU, Memory, HardDrive 如何交互,只需要调用 start_computer 即可。三、 行为型模式
关注对象之间的职责分配和通信。
7. 策略模式
- 意图: 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户而变化。
- Python 实现: 利用 Python 的“一等函数”特性,可以非常简单和自然地实现。
- 场景案例: 多种数据验证策略;多种排序算法;多种折扣计算方式。
- 代码解析:
from abc import ABC, abstractmethod
from typing import List
# 策略接口
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: List) -> List:
pass
# 具体策略
class BubbleSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
print("Sorting using bubble sort")
# ... 实现冒泡排序
return sorted(data) # 这里用内置sorted模拟
class QuickSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
print("Sorting using quick sort")
# ... 实现快速排序
return sorted(data) # 这里用内置sorted模拟
# 上下文 (Context)
class Sorter:
def __init__(self, strategy: SortStrategy = None):
# 默认策略
self._strategy = strategy or BubbleSortStrategy()
def set_strategy(self, strategy: SortStrategy):
# 动态设置策略
self._strategy = strategy
def execute_sort(self, data: List) -> List:
# 将工作委托给策略对象
return self._strategy.sort(data)
# 使用
data = [5, 2, 8, 1, 9]
sorter = Sorter()
sorter.set_strategy(BubbleSortStrategy())
result = sorter.execute_sort(data)
sorter.set_strategy(QuickSortStrategy())
result = sorter.execute_sort(data)
# Pythonic 的实现:直接使用函数
def bubble_sort(data):
print("Sorting using bubble sort (function)")
return sorted(data)
def quick_sort(data):
print("Sorting using quick sort (function)")
return sorted(data)
class SorterSimple:
def __init__(self, strategy_func=bubble_sort):
self.strategy_func = strategy_func
def execute_sort(self, data):
return self.strategy_func(data)
sorter_simple = SorterSimple(strategy_func=quick_sort)
result = sorter_simple.execute_sort(data)8. 观察者模式
- 意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- Python 实现: 使用列表存储观察者,并提供注册/注销方法。
- 场景案例: GUI 事件处理;消息推送系统;实现发布-订阅机制。
- 代码解析:
# 主题/被观察者
class Subject:
def __init__(self):
self._observers = [] # 存储观察者列表
def attach(self, observer):
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer):
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self, data=None):
"""通知所有观察者"""
for observer in self._observers:
observer.update(data)
# 观察者接口
class Observer(ABC):
@abstractmethod
def update(self, data):
pass
# 具体观察者
class EmailAlert(Observer):
def update(self, data):
print(f"Email Alert: Received data -> {data}")
class SMSAlert(Observer):
def update(self, data):
print(f"SMS Alert: Received data -> {data}")
# 使用
# 创建一个主题(比如一个天气数据源)
weather_station = Subject()
# 创建观察者
email_alert = EmailAlert()
sms_alert = SMSAlert()
# 注册观察者
weather_station.attach(email_alert)
weather_station.attach(sms_alert)
# 当状态改变时,通知所有观察者
weather_station.notify("Temperature is 25°C")
# 输出:
# Email Alert: Received data -> Temperature is 25°C
# SMS Alert: Received data -> Temperature is 25°C
weather_station.detach(sms_alert)
weather_station.notify("Temperature is 26°C")
# 只输出: Email Alert: Received data -> Temperature is 26°C四、总结与建议
| 模式 | 分类 | 核心思想 | Python 特点 |
|---|---|---|---|
| 单例 | 创建型 | 控制实例数量 | 常用模块或元类实现,模块是天然单例 |
| 工厂方法 | 创建型 | 子类决定创建对象 | 常用函数作为简单工厂,非常灵活 |
| 抽象工厂 | 创建型 | 创建产品家族 | 适用于需要创建一整套相关对象的场景 |
| 适配器 | 结构型 | 转换接口,兼容 | 通过组合实现,用于集成旧代码或第三方库 |
| 装饰器 | 结构型 | 动态添加职责 | Python 语法级支持 (@),应用极其广泛 |
| 外观 | 结构型 | 简化复杂子系统 | 提供简洁的 API,隐藏内部复杂性 |
| 策略 | 行为型 | 封装可互换的算法 | Python 中函数是一等对象,实现非常简单 |
| 观察者 | 行为型 | 状态变化通知 | 实现发布-订阅机制,用于事件驱动编程 |
建议:
- 不要强迫症: 不是所有问题都必须用设计模式解决。Python 强调简洁和可读性。如果一个简单函数或字典就能解决问题,那就用它。
- 利用语言特性: 深刻理解 Python 的一等函数、鸭子类型、元类、上下文管理器等特性,它们常常能以更优雅的方式实现模式的目标(例如,用函数代替策略类,用模块实现单例)。
- 识别模式,而非套用模式: 学习设计模式主要是为了拥有更好的词汇来描述解决方案和识别他人代码中的结构。当你遇到特定问题时,能想到“哦,这里可以用一个策略模式来解耦”,而不是为了用模式而用模式。
- 关注 Pythonic 的方式:
@decorator、contextlib、collections.abc等标准库工具本身就体现了某些模式的思想。优先使用这些 Pythonic 的工具。
四、异常处理
异常处理是编写健壮(Robust)程序的关键部分。它允许程序在遇到错误时不会突然崩溃,而是给你一个捕获错误、处理错误甚至记录错误的机会,从而优雅地降级或恢复。
一、异常处理的基本语法:try...except...else...finally
Python 使用 try 和 except 块来捕获和处理异常。
try块:- 放置可能引发异常的代码。
- 一旦块内的代码发生异常,后续代码将不会被执行,控制流会立即跳转到对应的
except块。 except块:- 用于捕获和处理特定的异常。
- 你可以指定要捕获的异常类型(例如
except ValueError:)。 - 一个
try语句可以有多个except子句,以处理不同类型的异常。 - 你也可以使用一个
except语句捕获多个异常:except (RuntimeError, TypeError, NameError):。 - 如果不指定异常类型,它将捕获所有异常(这是一个不好的实践,因为它会隐藏编程错误)。
else块(可选):- 必须放在所有
except块之后。 - 只有当
try块中的代码没有引发任何异常时,else块中的代码才会被执行。 - 这常用于将“仅当
try成功时才需要运行的代码”与try代码本身分离,使逻辑更清晰。 finally块(可选):- 无论
try块是否发生异常,finally块中的代码总是会被执行。 - 这通常用于执行一些清理工作,例如关闭文件、释放网络连接、释放锁等,确保资源在任何情况下都能被正确释放。
执行流程:
try -> (若异常发生) -> except -> finally
try -> (若无异常) -> else -> finally
二、常见的标准异常类型
Exception: 几乎所有内置异常的基类。通常捕获这个就足以处理大多数错误。ValueError: 当操作或函数接收到具有正确类型但值不合适的参数时引发。例如int('abc')。TypeError: 当操作或函数应用于不适当类型的对象时引发。例如'2' + 2。IndexError: 当序列下标超出范围时引发。KeyError: 当在字典中查找一个不存在的键时引发。FileNotFoundError: 当试图打开一个不存在的文件时引发。ZeroDivisionError: 除以零时引发。IOError: 输入/输出操作失败时引发(如磁盘已满、文件不存在等,FileNotFoundError是它的子类)。AttributeError: 当属性引用或赋值失败时引发。
三、主动抛出异常:raise 语句
你可以使用 raise 语句主动触发异常。
raise SomeException(‘Error message’)- 这通常用于在函数中检查参数有效性,或在满足某些错误条件时,通知调用者。
四、自定义异常
通过创建一个继承自 Exception 类(或其子类)的新类,你可以定义自己的异常类型。这使得你的错误更具体,更容易被捕获和处理。
class MyCustomError(Exception):
"""A custom exception for my application."""
pass五、获取异常信息:as 关键字
你可以使用 as 关键字将捕获的异常赋值给一个变量,从而访问它的错误信息。
try:
# ... some code
except ValueError as e:
print(f"Oops! A ValueError occurred: {e}")六、综合案例:一个简单的用户配置文件读取器
让我们编写一个程序,尝试从文件 config.json 中读取用户配置。这个案例会演示如何处理文件不存在、文件内容格式错误、以及特定配置项缺失等多种异常。
案例:一个简单的用户配置文件读取器
import json
# 1. 定义一个自定义异常,用于表示配置项缺失
class ConfigKeyError(Exception):
"""Exception raised for errors in the configuration keys."""
def __init__(self, key, message="Configuration key is missing"):
self.key = key
self.message = f"{message}: {key}"
super().__init__(self.message)
def load_config(filename):
"""
加载并解析JSON配置文件
"""
config = None
try:
# 尝试打开并读取文件
with open(filename, 'r') as f:
config = json.load(f) # 可能引发 FileNotFoundError, JSONDecodeError
print("✅ Config file loaded and parsed successfully.")
except FileNotFoundError:
# 处理文件不存在的异常
print(f"❌ Error: The config file '{filename}' was not found.")
# 可以在这里提供一个默认配置或重新抛出异常
# raise # 取消注释这行会让程序在此处终止并抛出异常
except json.JSONDecodeError as e:
# 处理JSON格式错误
print(f"❌ Error: Failed to parse the JSON config file. Invalid JSON: {e}")
finally:
# 无论成功与否,都执行清理工作(虽然`with open`会自动关闭文件,这里只是演示finally的用法)
print(f"Attempt to load config from '{filename}' is complete.\n")
return config # 如果出错,这里返回的是None
def get_database_url(config):
"""
从配置字典中获取数据库URL
"""
try:
# 尝试获取嵌套的配置项
url = config['database']['url'] # 可能引发 KeyError, TypeError
return url
except KeyError as e:
# 捕获因为键不存在而引发的KeyError
# 抛出我们自定义的、更清晰的异常
missing_key = e.args[0]
raise ConfigKeyError(missing_key)
except TypeError as e:
# 如果config是None或者config['database']不是字典,会引发TypeError
if config is None:
raise ValueError("Config is None. Cannot get database URL.") from e
else:
raise
# ---------------- 主程序 ----------------
config_file = 'config.json'
# 1. 加载配置
config_data = load_config(config_file)
# 2. 只有成功加载配置后,才尝试获取具体配置
if config_data:
try:
db_url = get_database_url(config_data)
print(f"🎯 Database URL is: {db_url}")
except ConfigKeyError as e:
print(f"⚠️ Configuration Error: {e}")
except ValueError as e:
print(f"⚠️ Error: {e}")
# 3. 如果加载失败(config_data为None),执行其他逻辑
else:
print("ℹ️ Using default configuration or exiting.")
# ... 这里可以设置默认配置案例中可能发生的情况及处理:
config.json文件不存在:open()引发FileNotFoundError。- 被第一个
except块捕获,打印友好错误信息。 load_config函数返回None。- 主程序的
if config_data:为假,执行else块,使用默认配置。 config.json文件存在但内容不是合法 JSON:json.load(f)引发json.JSONDecodeError。- 被第二个
except块捕获,打印解析错误信息。 - 函数返回
None,主程序同样进入else块。 - 文件存在且格式正确,但缺少
database或url键: config[‘database’][‘url’]会引发KeyError。- 在
get_database_url函数中被捕获,然后抛出自定义的ConfigKeyError,信息更明确(如"Configuration key is missing: url")。 - 主程序中的
except ConfigKeyError as e:块捕获到这个自定义异常并打印。 - 文件存在且格式正确,但
config加载失败返回了None: - 调用
get_database_url(None)时,config[‘database’]会对None进行订阅操作,引发TypeError。 - 在
get_database_url的except TypeError块中,我们检查到config is None,然后抛出一个更清晰的ValueError。 - 主程序中的
except ValueError as e:块捕获并打印这个错误。
通过这种方式,程序能够优雅地处理各种错误情况,向用户提供清晰明了的错误信息,而不是一堆令人困惑的栈跟踪(Traceback),同时保证了程序的稳定性。
五、并发编程
Python 提供了多种并发编程的方式,每种方式都有其适用场景和特点。由于全局解释器锁(GIL)的存在,Python 的并发模型与其他语言有所不同。
一、并发编程模型概览
1. 多线程 (Threading)
- 特点:共享内存,适合I/O密集型任务
- 优势:创建和切换成本低,共享数据方便
- 劣势:受GIL限制,不能真正并行执行CPU密集型任务
- 适用场景:网络请求、文件I/O、GUI应用
2. 多进程 (Multiprocessing)
- 特点:独立内存空间,适合CPU密集型任务
- 优势:绕过GIL限制,真正并行执行
- 劣势:创建和切换成本高,进程间通信复杂
- 适用场景:计算密集型任务、科学计算
3. 异步I/O (Asyncio)
- 特点:单线程事件循环,适合高并发I/O操作
- 优势:极高的I/O并发性能,资源消耗小
- 劣势:需要特定异步库支持,代码结构复杂
- 适用场景:网络服务器、高并发Web应用
4. 并发 Futures
- 特点:高级抽象,统一了线程和进程接口
- 优势:简单易用,代码统一
- 劣势:控制粒度较粗
- 适用场景:简单的并行任务执行
二、详细技术与案例
1. 多线程 (Threading) 案例:网页内容下载器
import threading
import requests
import time
from queue import Queue
from urllib.parse import urljoin
class WebPageDownloader:
def __init__(self, max_threads=5):
self.max_threads = max_threads
self.queue = Queue()
self.results = {}
self.lock = threading.Lock()
def download_page(self, url):
"""下载单个页面的函数"""
try:
response = requests.get(url, timeout=10)
with self.lock: # 使用锁保护共享资源
self.results[url] = {
'content': response.text[:200] + "...", # 只存储前200字符
'status': response.status_code,
'size': len(response.content)
}
print(f"Downloaded: {url} (Size: {len(response.content)} bytes)")
except Exception as e:
with self.lock:
self.results[url] = {'error': str(e)}
print(f"Failed to download {url}: {e}")
def worker(self):
"""工作线程函数"""
while True:
url = self.queue.get()
if url is None: # 退出信号
self.queue.task_done()
break
self.download_page(url)
self.queue.task_done()
def download_all(self, urls):
"""下载所有URL"""
# 创建并启动工作线程
threads = []
for _ in range(self.max_threads):
t = threading.Thread(target=self.worker)
t.start()
threads.append(t)
# 添加URL到队列
for url in urls:
self.queue.put(url)
# 等待所有任务完成
self.queue.join()
# 停止工作线程
for _ in range(self.max_threads):
self.queue.put(None)
for t in threads:
t.join()
return self.results
# 使用示例
if __name__ == "__main__":
urls = [
"https://www.python.org",
"https://www.github.com",
"https://www.stackoverflow.com",
"https://www.google.com",
"https://www.wikipedia.org"
]
downloader = WebPageDownloader(max_threads=3)
start_time = time.time()
results = downloader.download_all(urls)
end_time = time.time()
print(f"\nDownloaded {len(results)} pages in {end_time - start_time:.2f} seconds")
for url, data in results.items():
print(f"{url}: {data.get('status', 'Error')}")2. 多进程 (Multiprocessing) 案例:图像处理并行化
import multiprocessing as mp
import os
import time
from PIL import Image, ImageFilter
def process_image(args):
"""处理单个图像的函数"""
input_path, output_path, filter_type = args
try:
with Image.open(input_path) as img:
# 应用不同的滤镜
if filter_type == 'blur':
processed = img.filter(ImageFilter.BLUR)
elif filter_type == 'contour':
processed = img.filter(ImageFilter.CONTOUR)
elif filter_type == 'detail':
processed = img.filter(ImageFilter.DETAIL)
else:
processed = img.filter(ImageFilter.SHARPEN)
# 调整大小
processed = processed.resize((800, 600))
# 保存处理后的图像
processed.save(output_path)
return (input_path, output_path, "Success")
except Exception as e:
return (input_path, output_path, f"Error: {str(e)}")
def batch_process_images(input_dir, output_dir, filter_type='blur'):
"""批量处理图像"""
# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)
# 准备任务参数
tasks = []
for filename in os.listdir(input_dir):
if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
input_path = os.path.join(input_dir, filename)
output_path = os.path.join(output_dir, f"processed_{filename}")
tasks.append((input_path, output_path, filter_type))
# 使用进程池并行处理
start_time = time.time()
with mp.Pool(processes=mp.cpu_count()) as pool:
results = pool.map(process_image, tasks)
end_time = time.time()
# 输出结果
print(f"Processed {len(tasks)} images in {end_time - start_time:.2f} seconds")
for result in results:
print(f"{result[0]} -> {result[1]}: {result[2]}")
# 使用示例
if __name__ == "__main__":
input_directory = "input_images" # 替换为你的输入目录
output_directory = "output_images" # 替换为你的输出目录
batch_process_images(input_directory, output_directory, filter_type='blur')3. 异步I/O (Asyncio) 案例:高并发Web API客户端
import asyncio
import aiohttp
import time
import json
async def fetch_data(session, url, params=None):
"""异步获取数据"""
try:
async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=10)) as response:
data = await response.json()
return {"url": url, "status": response.status, "data": data}
except Exception as e:
return {"url": url, "error": str(e)}
async def fetch_all_urls(urls):
"""并发获取所有URL的数据"""
async with aiohttp.ClientSession() as session:
tasks = [fetch_data(session, url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
async def main():
"""主函数"""
# 模拟多个API端点
base_url = "https://jsonplaceholder.typicode.com"
endpoints = [
f"{base_url}/posts/{i}" for i in range(1, 11)
]
# 添加一些其他API
endpoints.extend([
f"{base_url}/users/1",
f"{base_url}/comments/5",
f"{base_url}/albums/1",
f"{base_url}/photos/1",
f"{base_url}/todos/1"
])
print(f"Fetching data from {len(endpoints)} endpoints...")
start_time = time.time()
results = await fetch_all_urls(endpoints)
end_time = time.time()
print(f"Completed in {end_time - start_time:.2f} seconds")
# 输出结果摘要
success_count = 0
error_count = 0
for result in results:
if isinstance(result, Exception) or "error" in result:
error_count += 1
print(f"Error fetching {result.get('url', 'Unknown')}: {result.get('error', str(result))}")
else:
success_count += 1
print(f"Success: {result['url']} (Status: {result['status']})")
print(f"\nSummary: {success_count} successful, {error_count} failed")
# 运行异步程序
if __name__ == "__main__":
asyncio.run(main())4. 并发 Futures 案例:并行计算任务
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
import time
import math
def is_prime(n):
"""检查一个数是否为质数(CPU密集型任务)"""
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def calculate_primes_count(start, end):
"""计算区间内质数的数量"""
count = 0
for num in range(start, end + 1):
if is_prime(num):
count += 1
return (start, end, count)
def parallel_prime_calculation(max_number, chunk_size=100000, use_processes=True):
"""并行计算质数数量"""
# 创建任务
tasks = []
for start in range(2, max_number, chunk_size):
end = min(start + chunk_size - 1, max_number)
tasks.append((start, end))
# 选择执行器
if use_processes:
executor_class = ProcessPoolExecutor
print("Using ProcessPoolExecutor (multiprocessing)")
else:
executor_class = ThreadPoolExecutor
print("Using ThreadPoolExecutor (multithreading)")
# 并行执行
start_time = time.time()
total_primes = 0
with executor_class(max_workers=4) as executor:
# 提交所有任务
future_to_task = {
executor.submit(calculate_primes_count, start, end): (start, end)
for start, end in tasks
}
# 处理完成的任务
for future in as_completed(future_to_task):
start, end, count = future.result()
total_primes += count
print(f"Range {start}-{end}: {count} primes")
end_time = time.time()
print(f"\nFound {total_primes} primes up to {max_number}")
print(f"Time taken: {end_time - start_time:.2f} seconds")
# 使用示例
if __name__ == "__main__":
# 计算10,000,000以内的质数数量
max_num = 10000000
# 使用多进程(适合CPU密集型任务)
print("=== Using Multiprocessing ===")
parallel_prime_calculation(max_num, use_processes=True)
print("\n" + "="*50 + "\n")
# 使用多线程(不适合CPU密集型任务,受GIL限制)
print("=== Using Multithreading ===")
parallel_prime_calculation(max_num, use_processes=False)三、选择指南
- I/O密集型任务(网络请求、文件操作):
- 优先选择:异步I/O (Asyncio)
- 次选:多线程 (Threading)
- CPU密集型任务(计算、图像处理):
- 优先选择:多进程 (Multiprocessing)
- 避免使用:多线程 (受GIL限制)
- 简单并行任务:
- 使用:并发 Futures (统一接口)
- 需要精细控制:
- 使用:底层多线程/多进程API
四、注意事项
- 全局解释器锁(GIL):限制了多线程在CPU密集型任务中的性能
- 进程间通信:多进程需要特殊机制(Queue, Pipe等)进行数据交换
- 资源消耗:多进程消耗更多内存,多线程消耗更多CPU上下文切换
- 调试难度:并发程序比串行程序更难调试
- 竞争条件:需要注意线程安全和同步问题
通过合理选择并发模型,可以显著提高Python程序的性能,特别是在处理I/O密集型任务或需要利用多核CPU的情况下。
六、生产者与消费者模型
生产者-消费者模型是一种经典的并发设计模式,用于解决多线程/多进程间的协作问题。该模型通过将数据的生产和使用分离,提高了系统的效率和可维护性。
一、模型核心概念
1. 基本组成
- 生产者:创建或提供数据的实体
- 消费者:处理或消耗数据的实体
- 缓冲区/队列:作为生产者和消费者之间的中介,平衡两者速度差异
2. 关键特性
- 解耦:生产者和消费者不需要直接交互
- 平衡负载:缓冲区可以平滑生产与消费的速度差异
- 支持并发:生产者和消费者可以并行工作
- 异步处理:生产者不需要等待消费者处理完成
二、模型实现方式
1. 线程/进程同步机制
- 互斥锁:保护共享资源(缓冲区)的访问
- 条件变量:协调生产者和消费者的执行
- 信号量:控制对有限资源的访问
2. 通信机制
- 队列/缓冲区:线程安全的数据结构
- 管道/套接字:进程间通信
- 消息队列:分布式系统间的通信
三、Python中的实现方式
1. 使用 threading 和 queue(多线程)
import threading
import queue
import time
import random
# 生产者类
class Producer(threading.Thread):
def __init__(self, queue, id):
super().__init__()
self.queue = queue
self.id = id
def run(self):
for i in range(5):
item = f"Item-{self.id}-{i}"
self.queue.put(item)
print(f"Producer {self.id} produced {item}")
time.sleep(random.uniform(0.1, 0.5))
print(f"Producer {self.id} finished")
# 消费者类
class Consumer(threading.Thread):
def __init__(self, queue, id):
super().__init__()
self.queue = queue
self.id = id
def run(self):
while True:
try:
item = self.queue.get(timeout=3) # 等待3秒超时
print(f"Consumer {self.id} consumed {item}")
time.sleep(random.uniform(0.1, 0.8))
self.queue.task_done()
except queue.Empty:
print(f"Consumer {self.id} timeout, exiting")
break
print(f"Consumer {self.id} finished")
# 创建队列和线程
q = queue.Queue()
producers = [Producer(q, i) for i in range(3)]
consumers = [Consumer(q, i) for i in range(2)]
# 启动所有线程
for p in producers:
p.start()
for c in consumers:
c.start()
# 等待生产者完成
for p in producers:
p.join()
# 等待所有任务完成
q.join()
# 等待消费者完成(它们会在超时后退出)
for c in consumers:
c.join()
print("All tasks completed")2. 使用 multiprocessing(多进程)
from multiprocessing import Process, Queue, current_process
import time
import random
def producer(queue, id, items_to_produce):
for i in range(items_to_produce):
item = f"Item-{id}-{i}"
queue.put(item)
print(f"Producer {id} produced {item}")
time.sleep(random.uniform(0.1, 0.3))
print(f"Producer {id} finished")
def consumer(queue, id):
while True:
item = queue.get()
if item is None: # 收到终止信号
queue.put(None) # 传递给下一个消费者
print(f"Consumer {id} received termination signal")
break
print(f"Consumer {id} consumed {item}")
time.sleep(random.uniform(0.2, 0.7))
print(f"Consumer {id} finished")
if __name__ == "__main__":
# 创建队列
q = Queue()
# 创建生产者进程
producers = [
Process(target=producer, args=(q, i, 5))
for i in range(3)
]
# 创建消费者进程
consumers = [
Process(target=consumer, args=(q, i))
for i in range(2)
]
# 启动所有进程
for p in producers:
p.start()
for c in consumers:
c.start()
# 等待生产者完成
for p in producers:
p.join()
# 发送终止信号给消费者
q.put(None)
# 等待消费者完成
for c in consumers:
c.join()
print("All processes completed")3. 使用 asyncio(异步IO)
import asyncio
import random
async def producer(queue, id, items_to_produce):
for i in range(items_to_produce):
item = f"Item-{id}-{i}"
await queue.put(item)
print(f"Producer {id} produced {item}")
await asyncio.sleep(random.uniform(0.1, 0.3))
print(f"Producer {id} finished")
async def consumer(queue, id):
while True:
try:
item = await asyncio.wait_for(queue.get(), timeout=2.0)
print(f"Consumer {id} consumed {item}")
await asyncio.sleep(random.uniform(0.2, 0.7))
queue.task_done()
except asyncio.TimeoutError:
print(f"Consumer {id} timeout, exiting")
break
print(f"Consumer {id} finished")
async def main():
# 创建队列
queue = asyncio.Queue(maxsize=10)
# 创建生产者和消费者任务
producers = [
asyncio.create_task(producer(queue, i, 5))
for i in range(3)
]
consumers = [
asyncio.create_task(consumer(queue, i))
for i in range(2)
]
# 等待生产者完成
await asyncio.gather(*producers)
# 等待队列为空
await queue.join()
# 取消消费者任务
for c in consumers:
c.cancel()
# 等待消费者完成(或被取消)
await asyncio.gather(*consumers, return_exceptions=True)
print("All tasks completed")
# 运行主函数
asyncio.run(main())四、高级实现:使用条件变量和锁
import threading
import time
import random
class BoundedBuffer:
def __init__(self, capacity):
self.capacity = capacity
self.buffer = []
self.lock = threading.Lock()
self.not_full = threading.Condition(self.lock)
self.not_empty = threading.Condition(self.lock)
def put(self, item):
with self.not_full:
while len(self.buffer) == self.capacity:
self.not_full.wait()
self.buffer.append(item)
self.not_empty.notify()
def get(self):
with self.not_empty:
while len(self.buffer) == 0:
self.not_empty.wait()
item = self.buffer.pop(0)
self.not_full.notify()
return item
def producer(buffer, id, items_to_produce):
for i in range(items_to_produce):
item = f"Item-{id}-{i}"
buffer.put(item)
print(f"Producer {id} produced {item}")
time.sleep(random.uniform(0.1, 0.3))
print(f"Producer {id} finished")
def consumer(buffer, id):
for i in range(7): # 每个消费者处理7个项目
item = buffer.get()
print(f"Consumer {id} consumed {item}")
time.sleep(random.uniform(0.2, 0.5))
print(f"Consumer {id} finished")
# 创建有界缓冲区
buffer = BoundedBuffer(5)
# 创建生产者和消费者线程
producers = [
threading.Thread(target=producer, args=(buffer, i, 5))
for i in range(3)
]
consumers = [
threading.Thread(target=consumer, args=(buffer, i))
for i in range(2)
]
# 启动所有线程
for p in producers:
p.start()
for c in consumers:
c.start()
# 等待所有线程完成
for p in producers:
p.join()
for c in consumers:
c.join()
print("All threads completed")五、实际应用案例:日志处理系统
import threading
import queue
import time
import random
from datetime import datetime
class LogProcessor:
def __init__(self, num_consumers=3):
self.log_queue = queue.Queue()
self.consumers = []
self.running = False
self.num_consumers = num_consumers
def start(self):
self.running = True
# 创建消费者
for i in range(self.num_consumers):
consumer = threading.Thread(target=self._consumer, args=(i,))
consumer.daemon = True
consumer.start()
self.consumers.append(consumer)
print("Log processor started")
def stop(self):
self.running = False
# 等待队列清空
self.log_queue.join()
print("Log processor stopped")
def add_log(self, log_message):
"""生产者方法:添加日志到队列"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"{timestamp} - {log_message}"
self.log_queue.put(log_entry)
print(f"Added log: {log_entry}")
def _consumer(self, id):
"""消费者方法:处理日志"""
while self.running:
try:
log_entry = self.log_queue.get(timeout=1)
# 模拟日志处理(存储到文件、数据库等)
print(f"Consumer {id} processing: {log_entry}")
# 随机模拟处理时间
time.sleep(random.uniform(0.1, 0.5))
self.log_queue.task_done()
except queue.Empty:
continue
def generate_test_logs(self, num_logs=10):
"""生成测试日志"""
log_types = ["INFO", "WARNING", "ERROR", "DEBUG"]
for i in range(num_logs):
log_type = random.choice(log_types)
message = f"{log_type}: Test log message {i}"
self.add_log(message)
time.sleep(random.uniform(0.05, 0.2))
# 使用示例
if __name__ == "__main__":
processor = LogProcessor(num_consumers=2)
processor.start()
# 生成一些测试日志
processor.generate_test_logs(15)
# 等待一段时间让消费者处理
time.sleep(2)
# 停止处理器
processor.stop()六、模型优势与挑战
优势:
- 解耦:生产者和消费者独立工作,互不依赖
- 平衡负载:缓冲区可以平滑处理速度差异
- 可扩展性:可以轻松增加生产者或消费者数量
- 复用性:生产者和消费者可以独立开发和测试
挑战:
- 缓冲区大小:需要合理设置缓冲区大小
- 死锁风险:不正确的同步可能导致死锁
- 资源管理:需要妥善管理线程/进程资源
- 错误处理:需要处理生产或消费过程中的异常
七、最佳实践
- 合理设置缓冲区大小:根据生产消费速度差设置合适的缓冲区
- 使用超时机制:避免消费者无限期等待
- 优雅关闭:确保所有任务完成后再关闭系统
- 监控和日志:记录系统运行状态,便于调试和优化
- 错误处理:妥善处qi理生产或消费过程中的异常
- 性能测试:在不同负载下测试系统性能
生产者-消费者模型是并发编程中的基础模式,掌握它对于构建高效、可扩展的并发系统至关重要。在实际应用中,可以根据具体需求选择合适的技术实现(线程、进程或异步IO)。
七、锁
在 Python 多线程编程中,锁(Lock) 是保证共享资源安全的核心机制。由于 Python 全局解释器锁(GIL)的存在,多线程在 CPU 密集型任务中并行性受限,但在 IO 密集型任务或需要保护共享资源(如全局变量、文件、数据库连接)时,锁仍然是必不可少的。
一、GIL锁(Global Interpreter Lock)
一、GIL的定义与核心作用
GIL(全局解释器锁) 是Python解释器(特指 CPython,即官方标准实现)中的一种互斥锁(Mutex),其核心作用是:确保同一时刻只有一个线程能执行Python字节码,即使在多核CPU环境下,Python多线程也无法实现真正的并行执行(针对CPU密集型任务)。
二、GIL的诞生背景:为何需要GIL?
GIL的设计源于CPython的内存管理机制。CPython使用引用计数(Reference Counting)管理内存:每个对象都有一个引用计数器,当计数器归0时自动释放内存。若多个线程同时操作同一对象的引用计数,可能导致竞争条件(Race Condition),例如:
- 线程A读取引用计数为5,准备+1;
- 线程B同时读取计数为5,准备-1;
- 最终计数可能变为5(而非正确的5+1-1=5,但中间过程可能因指令交错导致错误)。
为避免这种问题,GIL作为“全局锁”强制所有线程串行执行Python字节码,本质是用牺牲并行性换取内存管理的线程安全。
三、GIL的工作原理
GIL的核心逻辑可简化为以下步骤:
- 线程执行前必须获取GIL:任何线程要执行Python代码,需先申请GIL锁,若GIL被其他线程持有,则进入等待队列。
- 执行字节码并定期释放GIL:获取GIL的线程执行一段字节码后(默认执行100次字节码指令,可通过
sys.setcheckinterval()调整),会主动释放GIL,让其他线程有机会竞争。 - IO操作时主动释放GIL:若线程执行IO密集型任务(如网络请求、文件读写、sleep),会在等待IO时主动释放GIL,避免阻塞其他线程。
四、GIL对Python多线程性能的影响
GIL的存在直接影响多线程的执行效率,具体表现因任务类型而异:
1. 对CPU密集型任务:多线程无法并行,性能甚至不如单线程
CPU密集型任务(如数学计算、数据处理)的瓶颈是CPU算力。由于GIL限制,即使在多核CPU上,多线程也只能交替执行(伪并行),且线程切换会带来额外开销,导致性能可能低于单线程。
案例:多线程计算斐波那契数列(CPU密集型)
import threading
import time
def fib(n):
"""计算斐波那契数列第n项(CPU密集型)"""
if n <= 2:
return 1
return fib(n-1) + fib(n-2)
# 单线程执行
start = time.time()
fib(35)
fib(35)
print(f"单线程耗时: {time.time() - start:.2f}秒") # 约8秒(视CPU性能)
# 双线程执行(理论上应并行,但受GIL限制)
def run_fib():
fib(35)
start = time.time()
t1 = threading.Thread(target=run_fib)
t2 = threading.Thread(target=run_fib)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"双线程耗时: {time.time() - start:.2f}秒") # 约8秒(与单线程接近,甚至略长)结果分析:双线程并未因多核CPU而加速,因为GIL强制两个线程交替执行,总耗时与单线程接近,且线程切换会增加微小开销。
2. 对IO密集型任务:多线程可提升效率
IO密集型任务(如网络爬虫、文件读写、数据库操作)的瓶颈是等待IO响应(而非CPU算力)。此时线程会在等待IO时主动释放GIL,其他线程可获取GIL执行,因此多线程能有效利用等待时间,提升整体效率。
案例:多线程爬虫(IO密集型)
import threading
import time
import requests
def crawl(url):
"""爬取网页(IO密集型)"""
response = requests.get(url)
return response.status_code
# 单线程爬取5个网页
urls = ["https://www.baidu.com"] * 5
start = time.time()
for url in urls:
crawl(url)
print(f"单线程耗时: {time.time() - start:.2f}秒") # 约2秒(受网络延迟影响)
# 5线程爬取(同时发起5个请求)
start = time.time()
threads = [threading.Thread(target=crawl, args=(url,)) for url in urls]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"5线程耗时: {time.time() - start:.2f}秒") # 约0.5秒(并行等待IO,效率提升)结果分析:IO密集型任务中,多线程通过“并发等待IO”大幅减少总耗时,GIL此时不会成为瓶颈(线程在IO等待时释放GIL)。
五、GIL的常见误解
- “Python多线程完全没用”:错误。GIL仅影响CPU密集型任务,IO密集型任务(如爬虫、服务器)多线程仍能提升效率。
- “所有Python解释器都有GIL”:错误。GIL是CPython特有,其他解释器如Jython(Java实现)、IronPython(.NET实现)无GIL,可实现多线程并行。
- “GIL能保证线程安全”:错误。GIL仅保证字节码执行的串行性,但共享资源的修改仍需手动加锁(如全局变量)。例如:多线程同时修改全局计数器,即使有GIL,仍可能因“字节码指令交错”导致数据错误(见下文案例)。
六、GIL与线程安全:为何仍需手动加锁?
GIL仅确保“同一时刻只有一个线程执行字节码”,但单个Python语句可能对应多个字节码指令,仍可能出现竞争条件。例如:
# 全局计数器(共享资源)
count = 0
def increment():
global count
for _ in range(100000):
count += 1 # 看似原子操作,实则对应3个字节码指令:LOAD_GLOBAL(读count)→ INPLACE_ADD(+1)→ STORE_GLOBAL(写count)
# 2线程并发执行increment
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(count) # 结果可能小于200000(因GIL释放时,count的中间值可能被覆盖)原因:线程A执行到count += 1的“读count”后释放GIL,线程B读取相同的count值并+1,最终导致两次+1只生效一次。因此,即使有GIL,共享资源的修改仍需手动加锁(如threading.Lock)。
七、如何绕过GIL的限制?
针对CPU密集型任务,可通过以下方式规避GIL:
1. 使用多进程(multiprocessing)
多进程可突破GIL限制:每个进程有独立的Python解释器和GIL,可在多核CPU上并行执行。缺点是进程间通信成本高(需通过队列、管道等)。
from multiprocessing import Process
def fib(n):
if n <= 2:
return 1
return fib(n-1) + fib(n-2)
# 2进程并行计算(多核CPU上耗时约为单线程的1/2)
p1 = Process(target=fib, args=(35,))
p2 = Process(target=fib, args=(35,))
p1.start()
p2.start()
p1.join()
p2.join()2. 使用C扩展(释放GIL)
若核心逻辑用C/C++实现(如通过Cython、ctypes或C API),可在C代码中显式释放GIL,实现并行计算。例如:
// C代码示例:释放GIL后执行CPU密集型任务
#include <Python.h>
static PyObject* compute(PyObject* self, PyObject* args) {
Py_BEGIN_ALLOW_THREADS // 释放GIL
// 执行CPU密集型计算(无需Python API)
long result = 0;
for (long i=0; i<1e9; i++) result += i;
Py_END_ALLOW_THREADS // 重新获取GIL
return PyLong_FromLong(result);
}
<div id="code-wrapper-e669b2d3" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-e669b2d3')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### **3. 使用异步编程(asyncio)**
异步编程(单线程+事件循环)通过“非阻塞IO”实现并发,避免线程切换开销,适合IO密集型任务(如高并发服务器)。其本质是“单线程内的并发”,不涉及GIL竞争。
#### **八、总结**
GIL是CPython解释器的核心机制,其设计初衷是简化内存管理,但也带来了多线程并行性的限制。关键结论:
- **GIL仅影响CPU密集型任务**:多线程无法并行,需用多进程或C扩展绕过;
- **IO密集型任务不受GIL限制**:多线程可通过并发等待IO提升效率;
- **GIL不保证线程安全**:共享资源的修改仍需手动加锁(如`threading.Lock`);
- **GIL是CPython特有**:其他解释器(如Jython)或Python实现(如PyPy)可能无此限制。
理解GIL的原理和影响,是设计Python多线程/多进程程序的基础,需根据任务类型(CPU/IO密集)选择合适的并发模型。
## 二、常见锁类型及使用场景
### 1. **互斥锁(Mutex Lock)**
**定义**:最基础的锁,通过 `threading.Lock` 实现,确保同一时刻只有一个线程能获取锁并执行临界区代码,其他线程需等待锁释放。
**核心场景**:保护共享资源(如全局变量、计数器),防止并发修改导致的数据不一致。
#### **案例:多线程修改全局变量**
**问题**:多个线程同时修改同一全局变量,可能导致“竞态条件”(Race Condition),结果错误。
**解决方案**:用互斥锁保护修改变量的代码块。
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
count = 0 # 共享资源:全局计数器
lock = threading.Lock() # 创建互斥锁
def increment():
global count
for _ in range(100000): # 每个线程执行10万次自增
# 不加锁:count最终结果会小于200000(因线程切换导致中间值覆盖)
# 加锁:确保每次自增完整执行
with lock: # 上下文管理器自动acquire/release锁
count += 1
# 创建两个线程
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"最终count值: {count}") # 加锁后输出200000,不加锁可能远小于
<div id="code-wrapper-5ad95a99" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-5ad95a99')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
`with lock` 等价于 `lock.acquire()` 和 `lock.release()`,确保 `count += 1`(包含读取、修改、写入三步)作为原子操作执行,避免多线程交叉修改导致的错误。
### 2. **可重入锁(Reentrant Lock, RLock)**
**定义**:允许同一线程**多次获取同一把锁**(不会死锁),需释放相同次数才能完全释放锁。通过 `threading.RLock` 实现。
**核心场景**:递归函数、嵌套代码块需多次获取同一锁的场景。
#### **案例:递归函数中的锁**
**问题**:普通互斥锁(Lock)在递归中重复获取会导致死锁(线程等待自己释放锁)。
**解决方案**:使用 RLock 允许同一线程重复获取。
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
lock = threading.RLock() # 可重入锁(若用Lock会死锁)
def recursive_func(depth):
if depth == 0:
return
# 递归获取锁:同一线程可多次acquire
with lock:
print(f"递归深度: {depth}, 线程: {threading.current_thread().name}")
recursive_func(depth - 1) # 嵌套调用,再次获取锁
# 启动线程执行递归
t = threading.Thread(target=recursive_func, args=(3,))
t.start()
t.join()
# 输出(无死锁):
# 递归深度: 3, 线程: Thread-1
# 递归深度: 2, 线程: Thread-1
# 递归深度: 1, 线程: Thread-1
<div id="code-wrapper-012fbcb4" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-012fbcb4')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
RLock 内部维护“获取次数”计数器,每次 `acquire()` 计数器+1,`release()` 计数器-1,当计数器为0时锁才真正释放。普通 Lock 无此机制,递归调用时第二次 `acquire()` 会阻塞,导致死锁。
### 3. **信号量(Semaphore)**
**定义**:限制同时访问资源的**线程数量**(允许多个线程同时持有锁),通过 `threading.Semaphore` 实现。
**核心场景**:控制并发量(如限制最大连接数、限流)。
#### **案例:限制并发爬虫线程数**
**问题**:无限制创建线程爬取网页可能导致目标服务器过载或本地资源耗尽。
**解决方案**:用信号量限制同时运行的爬虫线程数。
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
# 信号量:最多允许3个线程同时执行
semaphore = threading.Semaphore(3)
def crawl(url):
with semaphore: # 获取信号量(计数器-1),若计数器为0则等待
print(f"爬取 {url},线程: {threading.current_thread().name}")
time.sleep(2) # 模拟网络请求耗时
print(f"完成 {url}")
# 创建5个爬虫线程(但同时最多3个运行)
urls = [f"https://example.com/page{i}" for i in range(5)]
threads = [threading.Thread(target=crawl, args=(url,)) for url in urls]
for t in threads:
t.start()
for t in threads:
t.join()
<div id="code-wrapper-f1e49f05" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-f1e49f05')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
输出
</code></pre><div class="code-expand-mask"></div></div></div>python
爬取 https://example.com/page0,线程: Thread-1
爬取 https://example.com/page1,线程: Thread-2
爬取 https://example.com/page2,线程: Thread-3
# 等待2秒后,前3个线程释放信号量,后2个开始执行
完成 https://example.com/page0
完成 https://example.com/page1
完成 https://example.com/page2
爬取 https://example.com/page3,线程: Thread-4
爬取 https://example.com/page4,线程: Thread-5
<div id="code-wrapper-da5dc76e" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-da5dc76e')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
信号量初始化时指定 `value=3`(允许3个线程同时访问),每个线程通过 `with semaphore` 获取“许可”,执行完后释放许可,供其他线程使用。
### 4. **事件(Event)**
**定义**:线程间通信的简单机制,通过 `set()` 发送信号、`wait()` 等待信号,`clear()` 重置信号。通过 `threading.Event` 实现。
**核心场景**:主线程控制子线程启动、多线程同步执行(如“预备-开始”场景)。
#### **案例:主线程控制子线程同时启动**
**问题**:多个子线程启动时间不同步,需等待所有线程准备就绪后统一开始。
**解决方案**:用 Event 让子线程等待主线程的“开始”信号。
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
start_event = threading.Event() # 初始为False(未触发)
def worker(name):
print(f"Worker {name} 准备就绪")
start_event.wait() # 等待事件触发(阻塞直到set()被调用)
print(f"Worker {name} 开始执行")
time.sleep(1)
# 创建3个工作线程
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
# 主线程等待所有子线程准备就绪(模拟初始化过程)
time.sleep(2)
print("所有Worker准备完毕,发送开始信号...")
start_event.set() # 触发事件,所有wait()的线程同时继续执行
for t in threads:
t.join()
<div id="code-wrapper-1342cf8d" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-1342cf8d')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
输出:
</code></pre><div class="code-expand-mask"></div></div></div>python
Worker 0 准备就绪
Worker 1 准备就绪
Worker 2 准备就绪
所有Worker准备完毕,发送开始信号...
Worker 0 开始执行
Worker 1 开始执行
Worker 2 开始执行
<div id="code-wrapper-7ed037e7" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-7ed037e7')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
`start_event.wait()` 会阻塞子线程,直到主线程调用 `start_event.set()`(将事件状态设为 True),此时所有等待的子线程同时被唤醒。
### 5. **条件变量(Condition)**
**定义**:更复杂的线程同步机制,允许线程等待**特定条件**满足后再执行,通过 `threading.Condition` 实现。需结合锁使用(内部封装了锁)。
**核心场景**:生产者-消费者模型(队列满时生产者等待,队列空时消费者等待)。
#### **案例:生产者-消费者模型**
**问题**:生产者和消费者共用一个队列,需避免队列满时继续生产、队列空时继续消费。
**解决方案**:用 Condition 等待“队列不满”或“队列非空”条件。
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
from queue import Queue
queue = Queue(maxsize=5) # 缓冲区队列,最多存5个元素
condition = threading.Condition() # 条件变量
def producer():
for i in range(10): # 生产10个元素
with condition:
# 若队列满,等待消费者消费(释放锁并阻塞)
while queue.full():
print("队列满,生产者等待...")
condition.wait() # 等待时释放内部锁,被唤醒后重新获取锁
queue.put(i)
print(f"生产者放入: {i},队列大小: {queue.qsize()}")
condition.notify() # 通知消费者:队列非空
def consumer():
for _ in range(10): # 消费10个元素
with condition:
# 若队列空,等待生产者生产
while queue.empty():
print("队列空,消费者等待...")
condition.wait()
item = queue.get()
print(f"消费者取出: {item},队列大小: {queue.qsize()}")
condition.notify() # 通知生产者:队列不满
# 启动生产者和消费者线程
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
t1.start()
t2.start()
t1.join()
t2.join()
<div id="code-wrapper-d253ea31" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-d253ea31')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
- `condition.wait()`:释放内部锁并阻塞,直到被 `notify()` 唤醒,唤醒后重新获取锁并检查条件。
- `condition.notify()`:唤醒一个等待的线程(`notify_all()` 唤醒所有)。
- 用 `while` 循环检查条件(而非 `if`),避免“虚假唤醒”(线程可能在条件未满足时被唤醒)。
### 6. **屏障(Barrier)**
**定义**:让**多个线程等待彼此**到达某个“屏障点”,所有线程到达后才继续执行。通过 `threading.Barrier` 实现。
**核心场景**:多线程分阶段执行任务(如所有线程完成初始化后,同时开始处理数据)。
#### **案例:多线程同步初始化**
**问题**:多个线程需完成各自的初始化步骤后,才能一起执行下一步。
**解决方案**:用 Barrier 等待所有线程到达初始化完成点。
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
# 屏障:等待3个线程到达后一起通过
barrier = threading.Barrier(3)
def worker(name):
print(f"Worker {name}: 开始初始化")
time.sleep(name) # 模拟不同初始化耗时(0s, 1s, 2s)
print(f"Worker {name}: 初始化完成,等待其他线程...")
barrier.wait() # 到达屏障点,等待其他线程
print(f"Worker {name}: 所有线程就绪,开始工作")
# 创建3个线程(编号0,1,2)
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
<div id="code-wrapper-d0d12a1c" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-d0d12a1c')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
输出:
</code></pre><div class="code-expand-mask"></div></div></div>python
Worker 0: 开始初始化
Worker 1: 开始初始化
Worker 2: 开始初始化
Worker 0: 初始化完成,等待其他线程...
# 等待1秒后,Worker1完成
Worker 1: 初始化完成,等待其他线程...
# 再等待1秒后,Worker2完成
Worker 2: 初始化完成,等待其他线程...
# 所有线程到达屏障点,同时继续执行
Worker 0: 所有线程就绪,开始工作
Worker 1: 所有线程就绪,开始工作
Worker 2: 所有线程就绪,开始工作
<div id="code-wrapper-cc3ad5df" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-cc3ad5df')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
`Barrier(3)` 要求3个线程调用 `wait()`,当第3个线程调用 `wait()` 时,所有线程同时被释放,继续执行后续代码。
## 三、常见锁问题及解决方案
### 1. **死锁(Deadlock)**
**定义**:两个或多个线程互相持有对方所需的锁,导致无限等待(程序卡死)。
#### **案例:循环等待锁**
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread1():
with lock_a:
print("Thread1: 获取lock_a")
time.sleep(1) # 给Thread2获取lock_b的时间
with lock_b: # 尝试获取lock_b,此时Thread2已持有lock_b
print("Thread1: 获取lock_b")
def thread2():
with lock_b:
print("Thread2: 获取lock_b")
time.sleep(1) # 给Thread1获取lock_a的时间
with lock_a: # 尝试获取lock_a,此时Thread1已持有lock_a
print("Thread2: 获取lock_a")
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start()
t1.join() # 程序卡死,无法继续执行
t2.join()
<div id="code-wrapper-b1b37e7f" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-b1b37e7f')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**原因**:
Thread1 持有 lock_a 等待 lock_b,Thread2 持有 lock_b 等待 lock_a,形成循环等待。
**解决方案**:
- **按固定顺序获取锁**:所有线程严格按同一顺序获取锁(如先 lock_a 后 lock_b)。
- **设置超时时间**:用 `lock.acquire(timeout=5)`,超时后释放已持有的锁并重试。
- **使用死锁检测工具**:如 `threading._allocate_lock()` 的调试功能或第三方库 `py-spy`。
### 2. **活锁(Livelock)**
**定义**:线程不断尝试获取锁,但因“过度谦让”导致无法前进(类似死锁,但线程处于活跃状态)。
#### **案例:过度重试导致的活锁**
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
def thread1(lock1, lock2):
while True:
with lock1:
print("Thread1: 获取lock1")
time.sleep(0.1)
# 尝试获取lock2,失败则释放lock1重试
if lock2.acquire(timeout=0.1):
print("Thread1: 获取lock2,完成")
lock2.release()
break
print("Thread1: 释放lock1,重试")
def thread2(lock1, lock2):
while True:
with lock2:
print("Thread2: 获取lock2")
time.sleep(0.1)
# 尝试获取lock1,失败则释放lock2重试
if lock1.acquire(timeout=0.1):
print("Thread2: 获取lock1,完成")
lock1.release()
break
print("Thread2: 释放lock2,重试")
lock1 = threading.Lock()
lock2 = threading.Lock()
t1 = threading.Thread(target=thread1, args=(lock1, lock2))
t2 = threading.Thread(target=thread2, args=(lock1, lock2))
t1.start()
t2.start()
<div id="code-wrapper-baca9030" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-baca9030')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**原因**:
两个线程获取一个锁后,尝试获取另一个锁失败,立即释放当前锁并重试,导致无限循环。
**解决方案**:
- **引入随机延迟**:重试前随机等待一段时间,降低冲突概率。
- **按优先级获取锁**:固定线程获取锁的优先级,避免互相谦让。
### 3. **饥饿(Starvation)**
**定义**:某个线程因优先级低或资源被高优先级线程长期占用,导致**长期无法获取锁**。
#### **案例:高优先级线程占用锁导致低优先级线程饥饿**
</code></pre><div class="code-expand-mask"></div></div></div>python
import threading
import time
lock = threading.Lock()
def high_priority():
while True:
with lock:
print("高优先级线程:占用锁")
time.sleep(0.1) # 频繁占用锁,每次占用时间短
def low_priority():
count = 0
while count < 3: # 尝试获取3次锁
with lock:
print("低优先级线程:获取锁")
count += 1
time.sleep(0.5) # 每次占用锁时间长
# 启动高优先级线程(后台线程)
t1 = threading.Thread(target=high_priority, daemon=True)
t1.start()
# 启动低优先级线程
t2 = threading.Thread(target=low_priority)
t2.start()
t2.join()
<div id="code-wrapper-f010dbeb" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-f010dbeb')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**原因**:
高优先级线程频繁获取并释放锁,低优先级线程因调度机会少,难以竞争到锁资源。
**解决方案**:
- **使用公平锁**:确保线程按请求顺序获取锁(Python 锁默认非公平,可通过第三方库实现公平锁)。
- **限制高优先级线程执行时间**:避免长时间占用锁。
- **批量处理任务**:减少锁的获取频率。
## 四、使用锁的注意事项
1. **最小化锁范围**:仅在修改共享资源的**临界区**加锁,避免锁持有时间过长(如将 IO 操作移出锁范围)。
2. **优先使用上下文管理器**:通过 `with lock` 自动管理 `acquire()` 和 `release()`,避免遗漏释放导致死锁。
3. **避免嵌套锁**:嵌套锁增加死锁风险,如需使用,确保所有线程按同一顺序获取锁。
4. 考虑 GIL 的影响:
- GIL 保证同一时刻只有一个线程执行 Python 字节码,但**不保护共享资源**(需手动加锁)。
- IO 密集型任务(如网络请求、文件读写):线程会释放 GIL 等待 IO,适合用锁保护共享资源。
- CPU 密集型任务:多线程因 GIL 限制无法并行,建议用多进程(`multiprocessing`)+ 进程锁(`multiprocessing.Lock`)。
5. **选择合适的锁类型**:简单共享资源用互斥锁,递归场景用 RLock,限流用 Semaphore,复杂同步用 Condition。
## 总结
Python 提供了丰富的锁机制(互斥锁、可重入锁、信号量等),用于解决多线程共享资源安全问题。实际开发中需根据场景选择锁类型,并警惕死锁、活锁、饥饿等问题。核心原则是:**最小化锁粒度、避免嵌套锁、优先使用上下文管理器**,同时结合 GIL 特性合理设计多线程模型。
# 八、协程
#### **一、协程的定义与核心概念**
**协程(Coroutine)** 是一种**用户态的轻量级“线程”**,由程序自身(而非操作系统内核)控制调度。它允许在单线程内实现多任务的“并发执行”,通过主动让出 CPU 控制权(而非抢占式调度)实现任务切换,因此**切换成本极低**(无需内核参与上下文切换),适合高并发 IO 密集型场景。
### **二、协程的核心特点**
| **特点** | **说明** |
| ----------------- | ------------------------------------------------------------ |
| **轻量级** | 一个 Python 进程可创建数十万协程(内存占用仅几 KB/个),远多于线程/进程。 |
| **协作式调度** | 协程通过 `await` 主动让出控制权,事件循环(Event Loop)调度其他就绪协程,避免线程的抢占式切换开销。 |
| **单线程内并发** | 所有协程在同一线程内运行,规避 GIL 对多线程并行的限制,也无需处理线程安全问题(共享资源无需加锁)。 |
| **依赖事件循环** | 需通过事件循环(如 `asyncio`)管理协程的创建、调度、暂停与恢复。 |
| **IO 密集型友好** | 适用于网络请求、文件读写等 IO 等待场景,通过并发等待 IO 提升效率;CPU 密集型任务则会阻塞事件循环,不适用。 |
### **三、Python 协程的发展与实现方式**
Python 协程的实现经历了从基础语法到标准库支持的演进:
#### **1. 早期实现:`yield` 与生成器(Generator)模拟**
Python 2.x 中无原生协程,可通过生成器的 `yield` 关键字实现简单协程(利用生成器的暂停/恢复能力)。
**核心原理**:生成器函数通过 `yield` 暂停执行并返回值,调用方通过 `send()` 恢复执行,实现“协作式切换”。
**案例:用生成器模拟协程切换**
</code></pre><div class="code-expand-mask"></div></div></div>python
def coroutine1():
for i in range(3):
print(f"协程1:执行第 {i+1} 步")
yield # 暂停,让出控制权
print(f"协程1:从暂停中恢复")
def coroutine2():
for i in range(3):
print(f"协程2:执行第 {i+1} 步")
yield # 暂停,让出控制权
print(f"协程2:从暂停中恢复")
# 创建生成器(模拟协程)
c1 = coroutine1()
c2 = coroutine2()
# 手动调度:交替执行两个“协程”
for _ in range(3):
next(c1) # 执行协程1,到 yield 暂停
next(c2) # 执行协程2,到 yield 暂停
<div id="code-wrapper-cd30f99e" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-cd30f99e')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:通过 `yield` 手动控制两个生成器交替执行,模拟了协程的“协作式调度”,但功能有限(无法直接处理 IO 事件)。
#### **2. 原生协程:`async/await` 语法(Python 3.5+)**
Python 3.5 引入 `async/await` 语法糖,3.7 加入 `asyncio.run()` 简化事件循环管理,正式支持**原生协程**。
- **协程函数**:用 `async def` 定义,调用后返回**协程对象**(而非直接执行)。
- **`await` 关键字**:在协程函数中,通过 `await` 暂停执行并等待另一个可等待对象(协程、`Task`、`Future` 等)完成,期间让出控制权。
- **事件循环**:协程需注册到事件循环中运行,由事件循环调度执行顺序(如 `asyncio.get_event_loop()` 或 `asyncio.run()`)。
### **四、协程核心组件与基础案例**
#### **1. 协程函数与协程对象**
</code></pre><div class="code-expand-mask"></div></div></div>python
import asyncio
# 定义协程函数(async def)
async def hello(name):
print(f"Hello, {name}")
await asyncio.sleep(1) # 模拟 IO 等待(必须用异步 sleep,而非 time.sleep)
print(f"Goodbye, {name}")
# 调用协程函数 → 返回协程对象(未执行)
coro = hello("Python")
print(type(coro)) # <class 'coroutine'>
# 运行协程:必须通过事件循环
asyncio.run(coro) # Python 3.7+ 推荐用法,自动管理事件循环
<div id="code-wrapper-0cec2f05" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-0cec2f05')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**关键说明**:
- `asyncio.sleep(1)` 是异步 IO 操作,调用时协程会暂停并让出控制权,事件循环可调度其他协程运行;
- `time.sleep(1)` 是同步阻塞操作,会导致整个事件循环卡住(**严禁在协程中使用同步 IO**)。
#### **2. 事件循环(Event Loop)**
事件循环是协程的“调度中心”,负责:
- 注册和执行协程/任务;
- 监控 IO 事件(如网络请求完成、定时器到期);
- 在协程暂停时切换到其他就绪协程。
**手动获取与运行事件循环(Python 3.6-)**:
</code></pre><div class="code-expand-mask"></div></div></div>python
loop = asyncio.get_event_loop() # 获取默认事件循环
loop.run_until_complete(hello("Python")) # 运行协程
loop.close() # 关闭事件循环(Python 3.7+ 可省略,asyncio.run() 自动处理)
<div id="code-wrapper-6ca33b10" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-6ca33b10')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### **3. 任务(Task):并发调度协程**
单个协程按顺序执行,**任务(Task)** 可将协程包装成可调度的单元,实现多个协程的并发执行(事件循环同时管理多个 Task)。
**创建 Task 的两种方式**:
- `asyncio.create_task(coro)`:Python 3.7+,高效(推荐);
- `loop.create_task(coro)` 或 `asyncio.ensure_future(coro)`:兼容旧版本。
**案例:并发执行多个协程**
</code></pre><div class="code-expand-mask"></div></div></div>python
import asyncio
import time
async def task(name, delay):
print(f"任务 {name} 启动")
await asyncio.sleep(delay) # 模拟 IO 等待
print(f"任务 {name} 完成(耗时 {delay} 秒)")
return delay # 任务返回结果
async def main():
start = time.time()
# 创建 3 个任务(并发执行)
task1 = asyncio.create_task(task("A", 1))
task2 = asyncio.create_task(task("B", 2))
task3 = asyncio.create_task(task("C", 3))
# 等待所有任务完成(可获取返回结果)
results = await asyncio.gather(task1, task2, task3)
print(f"所有任务结果: {results}")
print(f"总耗时: {time.time() - start:.2f} 秒")
asyncio.run(main())
<div id="code-wrapper-9c400da3" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-9c400da3')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**解析**:
- 3 个任务并发执行,总耗时等于最长任务(3秒);
- `asyncio.gather(*tasks)` 用于等待多个任务完成并收集结果,是并发调度的核心函数。
### **五、协程实战案例:异步爬虫(IO 密集型场景)**
协程最典型的应用是 **IO 密集型高并发任务**(如网络爬虫、API 服务),通过并发等待网络响应大幅提升效率。
#### **场景需求**:爬取多个网页,对比同步与异步耗时。
**同步爬虫(单线程顺序执行)**:
</code></pre><div class="code-expand-mask"></div></div></div>python
import requests
import time
def sync_crawl(urls):
start = time.time()
for url in urls:
requests.get(url) # 同步网络请求(阻塞等待)
print(f"爬取完成: {url}")
print(f"同步总耗时: {time.time() - start:.2f}秒")
urls = [f"https://httpbin.org/get?i={i}" for i in range(5)] # 5个测试URL
sync_crawl(urls)
<div id="code-wrapper-3c1331f7" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-3c1331f7')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**异步爬虫(协程并发执行)**:
需使用异步 HTTP 库(如 `aiohttp`,Python 异步网络标准库):
</code></pre><div class="code-expand-mask"></div></div></div>python
import asyncio
import aiohttp # async HTTP 客户端(需安装:pip install aiohttp)
import time
async def async_crawl(session, url):
async with session.get(url) as response: # 异步网络请求
print(f"爬取完成: {url}")
async def main(urls):
start = time.time()
async with aiohttp.ClientSession() as session: # 创建异步请求会话
# 创建任务列表(并发爬取所有URL)
tasks = [asyncio.create_task(async_crawl(session, url)) for url in urls]
await asyncio.gather(*tasks) # 等待所有任务完成
print(f"异步总耗时: {time.time() - start:.2f}秒")
urls = [f"https://httpbin.org/get?i={i}" for i in range(5)]
asyncio.run(main(urls))
<div id="code-wrapper-4a0c430d" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-4a0c430d')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**关键效果**:
异步爬虫通过**并发发送 5 个网络请求**,等待 Responses 的过程是并行的,总耗时≈单个请求耗时(0.5秒),效率是同步爬虫的≈5倍。
### **六、协程的高级应用:任务组与异常处理**
#### **1. 用 `asyncio.gather` 动态添加任务**
`asyncio.gather` 可接收多个任务,并按输入顺序返回结果:
</code></pre><div class="code-expand-mask"></div></div></div>python
async def add(a, b):
await asyncio.sleep(0.1)
return a + b
async def main():
# 并发执行 3 个加法任务
results = await asyncio.gather(add(1,2), add(3,4), add(5,6))
print(results) # [3, 7, 11]
asyncio.run(main())
<div id="code-wrapper-940248fb" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-940248fb')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### **2. 用 `asyncio.wait` 控制任务结束条件**
`asyncio.wait` 更灵活,可指定等待策略(如等待第一个任务完成、所有任务完成):
</code></pre><div class="code-expand-mask"></div></div></div>python
async def task(name, delay):
await asyncio.sleep(delay)
return name
async def main():
tasks = [
asyncio.create_task(task("A", 1)),
asyncio.create_task(task("B", 2)),
]
# 等待第一个任务完成(FIRST_COMPLETED)
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
print(f"完成的任务: {[t.result() for t in done]}") # ['A']
print(f"未完成的任务: {[t.result() for t in pending]}") # ['B'](需手动取消或等待)
asyncio.run(main())
<div id="code-wrapper-4c6f95ec" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-4c6f95ec')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### **3. 协程异常处理**
协程中未捕获的异常会导致事件循环崩溃,需通过 `try-except` 捕获:
</code></pre><div class="code-expand-mask"></div></div></div>python
async def risky_task():
await asyncio.sleep(0.1)
raise ValueError("任务执行失败!")
async def main():
try:
await risky_task()
except ValueError as e:
print(f"捕获异常: {e}") # 捕获异常: 任务执行失败!
asyncio.run(main()) # 事件循环正常退出,不崩溃
<div id="code-wrapper-9845fe3d" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-9845fe3d')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### **七、协程 vs 多线程 vs 多进程**
| **特性** | **协程(Coroutine)** | **多线程(Thread)** | **多进程(Process)** |
| ------------ | -------------------------- | -------------------------- | ----------------------------- |
| **调度方式** | 用户态协作(主动让出) | 内核态抢占(操作系统调度) | 内核调度(独立内存空间) |
| **切换成本** | 极低(微秒级) | 较高(毫秒级) | 极高(ms级,需复制内存) |
| **并发能力** | 单线程数十万级 | 受线程数限制(千级) | 受 CPU 核心数限制(数十级) |
| **共享资源** | 同线程共享,无需锁 | 需加锁(GIL限制) | 独立内存,需进程间通信(IPC) |
| **适用场景** | IO 密集型(爬虫、API服务) | IO 密集型(兼容性好) | CPU 密集型(利用多核) |
### **八、协程的局限性与注意事项**
1. **单线程限制**:所有协程在同一线程内运行,**CPU 密集型任务会阻塞整个事件循环**(需配合多进程使用,如 `multiprocessing` + 协程)。
2. **必须使用异步库**:协程中所有 IO 操作必须用异步库(如 `aiohttp` 代替 `requests`,`aiomysql` 代替 `pymysql`),否则会阻塞事件循环。
3. **调试难度高**:协程切换逻辑复杂,异常堆栈通常不包含完整调用链,调试需借助 `asyncio.debug` 模式。
4. **不适合强实时任务**:协作式调度可能导致某个协程长时间占用 CPU,需通过定时器强制切换(如 `asyncio.sleep(0)` 主动让出)。
### **九、总结**
Python 协程通过 `async/await` 语法和 `asyncio` 标准库,实现了用户态轻量级并发,**在 IO 密集型高并发场景(如网络爬虫、微服务 API、WebSocket 服务器)中性能远超多线程**。其核心优势是极低的切换成本和单线程内的高效调度,但需注意避开同步 IO 和 CPU 密集型任务的坑。
**典型应用场景**:
- 高并发网络爬虫(如爬取海量网页);
- 异步 Web 框架(如 FastAPI、Sanic,支持每秒数万请求);
- 分布式任务调度(如 Celery 异步任务队列);
- 实时数据处理(如日志流、传感器数据采集)。
了解协程的原理与实践,是 Python 高性能异步编程的核心基础。
# 九、网络编程
#### **一、基础概念**
网络编程是指通过计算机网络实现不同设备间的数据传输与通信,Python凭借丰富的标准库和第三方库,提供了从底层套接字到高层协议的完整支持。
##### **1. 网络通信模型**
- **OSI七层模型**:物理层→数据链路层→网络层→传输层→会话层→表示层→应用层(理论模型,便于理解网络分层职责)。
- TCP/IP四层模型
(实际应用):
- **网络接口层**(物理+数据链路层):处理硬件地址与数据帧。
- **网络层**:IP协议(网际协议),负责跨网络路由(如IP地址、子网掩码)。
- **传输层**:TCP(可靠传输)、UDP(高效传输),负责端到端数据传输。
- **应用层**:HTTP、FTP、SMTP等协议,定义数据交互规则(如浏览器与服务器的HTTP通信)。
##### **2. 核心协议对比**
| **协议** | **特点** | **适用场景** |
| -------- | ------------------------------------------------------------ | ------------------------------------------- |
| **TCP** | 面向连接、可靠传输(三次握手建立连接,四次挥手断开)、字节流传输、拥塞控制。 | 数据完整性要求高(文件传输、聊天、HTTP)。 |
| **UDP** | 无连接、不可靠(不保证送达/顺序)、数据报传输、速度快、开销小。 | 实时性要求高(视频流、语音通话、DNS查询)。 |
##### **3. 客户端-服务器(C/S)模型**
- **核心架构**:客户端主动发起请求,服务器被动监听并响应请求(如浏览器→Web服务器、APP→API服务器)。
- 关键概念
:
- **IP地址**:设备在网络中的唯一标识(如`192.168.1.1`)。
- **端口**:区分设备上的不同服务(0-65535,知名端口如HTTP:80、HTTPS:443)。
- **套接字(Socket)**:IP+端口的组合(如`(192.168.1.1, 8080)`),是网络通信的端点。
#### **二、主要模块**
Python网络编程依赖以下核心模块,覆盖从底层到高层的通信需求:
##### **1. 底层套接字编程:`socket`模块**
直接操作TCP/UDP套接字,实现基础网络通信(需手动处理连接、数据收发等细节)。
- 核心功能
:
- 创建套接字:`socket.socket(family=AF_INET, type=SOCK_STREAM)`(`AF_INET`:IPv4,`SOCK_STREAM`:TCP;`SOCK_DGRAM`:UDP)。
- 绑定端口:`socket.bind((host, port))`(服务器端)。
- 监听连接:`socket.listen(backlog)`(TCP服务器,`backlog`:最大等待连接数)。
- 接受连接:`socket.accept()`(TCP服务器,返回`(client_socket, client_addr)`)。
- 连接服务器:`socket.connect((host, port))`(客户端)。
- 收发数据:`send(data)/recv(buffer_size)`(TCP)、`sendto(data, addr)/recvfrom(buffer_size)`(UDP)。
##### **2. 高层服务器框架:`socketserver`模块**
简化TCP/UDP服务器开发,内置多线程/多进程支持(无需手动管理客户端连接)。
- **核心类**:`TCPServer`(TCP服务器)、`UDPServer`(UDP服务器)、`ThreadingTCPServer`(多线程TCP服务器)。
##### **3. HTTP客户端模块**
- `urllib`(标准库)
:支持HTTP/HTTPS请求,功能基础(需手动处理编码、异常)。
- 核心组件:`urllib.request`(发送请求)、`urllib.parse`(URL解析)。
- **`requests`(第三方库,推荐)**:简化HTTP操作,支持GET/POST、Cookie、会话管理等,语法简洁(需安装:`pip install requests`)。
##### **4. HTTP服务器模块**
- **`http.server`(标准库)**:实现简单HTTP服务器(仅用于测试,不适合生产环境)。
- **Web框架(第三方)**:如`Flask`(轻量级)、`Django`(全功能),快速开发Web服务(支持路由、模板、数据库等)。
##### **5. 异步网络编程:`asyncio`+`aiohttp`**
- **`asyncio`(标准库)**:提供事件循环,支持异步IO操作(协程调度)。
- **`aiohttp`(第三方)**:异步HTTP客户端/服务器,适合高并发IO场景(如异步爬虫、API服务)。
##### **6. 其他协议模块**
- `ftplib`(FTP文件传输)、`smtplib`(发送邮件)、`poplib`(接收邮件)、`telnetlib`(Telnet协议)等,覆盖特定协议需求。
#### **三、典型应用场景**
- **客户端编程**:网络请求(爬虫)、即时通信(聊天客户端)、API调用(如调用支付接口)。
- **服务器编程**:TCP聊天服务器、Web服务器(提供网页/API)、游戏服务器(实时数据交互)。
- **数据交换**:通过JSON/XML序列化网络数据(如客户端→服务器传输用户信息:`{"name": "Alice", "age": 20}`)。
- **网络安全**:`ssl`模块实现TCP加密(HTTPS基于SSL/TLS),保护数据传输安全。
#### **四、案例解析**
##### **案例1:TCP客户端与服务器(聊天程序)**
**TCP服务器**(监听端口,接收客户端消息并回复):
</code></pre><div class="code-expand-mask"></div></div></div>python
import socket
# 创建TCP套接字(IPv4,流式传输)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("0.0.0.0", 8080)) # 绑定本地所有IP+端口8080
server_socket.listen(5) # 最大等待连接数5
print("服务器启动,监听端口8080...")
# 接受客户端连接(阻塞,直到有连接)
client_socket, client_addr = server_socket.accept()
print(f"客户端{client_addr}已连接")
while True:
# 接收数据(缓冲区1024字节,需解码为字符串)
data = client_socket.recv(1024).decode("utf-8")
if not data or data == "exit":
break
print(f"客户端: {data}")
# 发送回复
reply = input("服务器回复: ")
client_socket.send(reply.encode("utf-8")) # 字符串→字节流
client_socket.close()
server_socket.close()
<div id="code-wrapper-8c308e9f" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-8c308e9f')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**TCP客户端**(连接服务器,发送/接收消息):
</code></pre><div class="code-expand-mask"></div></div></div>python
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(("127.0.0.1", 8080)) # 连接本地服务器
while True:
msg = input("客户端发送: ")
client_socket.send(msg.encode("utf-8"))
if msg == "exit":
break
# 接收服务器回复
reply = client_socket.recv(1024).decode("utf-8")
print(f"服务器: {reply}")
client_socket.close()
<div id="code-wrapper-59af72ee" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-59af72ee')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**运行效果**:
- 启动服务器→启动客户端→客户端输入消息→服务器接收并回复,实现双向聊天(输入`exit`退出)。
##### **案例2:UDP数据报传输**
UDP无连接,无需建立连接,直接发送数据报(适合简单广播/实时数据)。
**UDP服务器**:
</code></pre><div class="code-expand-mask"></div></div></div>python
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP套接字
server_socket.bind(("0.0.0.0", 8080))
while True:
# 接收数据报(返回数据+客户端地址)
data, client_addr = server_socket.recvfrom(1024)
print(f"收到{client_addr}的消息: {data.decode('utf-8')}")
# 回复客户端
server_socket.sendto(f"已收到: {data.decode('utf-8')}".encode("utf-8"), client_addr)
<div id="code-wrapper-3d399143" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-3d399143')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**UDP客户端**:
</code></pre><div class="code-expand-mask"></div></div></div>python
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_addr = ("127.0.0.1", 8080)
msg = "Hello UDP!"
client_socket.sendto(msg.encode("utf-8"), server_addr) # 直接发送数据报
data, _ = client_socket.recvfrom(1024) # 接收回复
print(f"服务器回复: {data.decode('utf-8')}") # 输出:已收到: Hello UDP!
<div id="code-wrapper-ba9308f7" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-ba9308f7')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### **案例3:HTTP客户端(用`requests`爬取网页)**
</code></pre><div class="code-expand-mask"></div></div></div>python
import requests
# 发送GET请求
url = "https://www.baidu.com"
response = requests.get(url)
response.encoding = "utf-8" # 指定编码(解决中文乱码)
print(f"状态码: {response.status_code}") # 200表示成功
print(f"网页内容: {response.text[:200]}") # 打印前200字符(网页HTML)
<div id="code-wrapper-740fa4c3" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-740fa4c3')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**扩展**:带参数的请求(如搜索):
</code></pre><div class="code-expand-mask"></div></div></div>python
params = {"wd": "Python网络编程"} # 搜索关键词
response = requests.get("https://www.baidu.com/s", params=params)
print(f"跳转后的URL: {response.url}") # 输出带参数的完整URL
<div id="code-wrapper-5c73e83e" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-5c73e83e')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### **案例4:简单Web服务器(用`Flask`)**
安装Flask:`pip install flask`,创建文件`app.py`:
</code></pre><div class="code-expand-mask"></div></div></div>python
from flask import Flask, jsonify
app = Flask(__name__)
# 定义路由(访问http://127.0.0.1:5000/hello触发)
@app.route("/hello")
def hello():
return "Hello, Web Server!"
# 返回JSON数据(API接口示例)
@app.route("/user")
def get_user():
return jsonify({"name": "Alice", "age": 20}) # 返回{"name": "Alice", "age": 20}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000) # 启动服务器,监听5000端口
<div id="code-wrapper-03c51abf" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-03c51abf')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
运行后访问`http://127.0.0.1:5000/hello`,浏览器显示`Hello, Web Server!`;访问`/user`返回JSON数据。
##### **案例5:异步HTTP请求(用`aiohttp`)**
高并发场景下,异步请求比同步请求效率提升显著(如爬取多个网页):
</code></pre><div class="code-expand-mask"></div></div></div>python
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response: # 异步GET请求
return await response.text() # 等待响应内容
async def main():
urls = [f"https://httpbin.org/get?i={i}" for i in range(5)] # 5个测试URL
async with aiohttp.ClientSession() as session: # 创建异步会话
# 创建任务列表(并发请求)
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks) # 等待所有任务完成
for i, html in enumerate(results):
print(f"URL {i} 内容长度: {len(html)}") # 打印每个响应的长度
asyncio.run(main()) # 运行异步主函数
<div id="code-wrapper-92d6da4f" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-92d6da4f')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
**效果**:5个请求并发执行,总耗时≈单个请求耗时(同步需5倍时间)。
#### **五、总结**
Python网络编程覆盖从底层套接字到高层协议的全栈能力,核心模块包括:
- **底层通信**:`socket`(TCP/UDP)、`socketserver`(服务器框架)。
- **HTTP交互**:`requests`(客户端)、`Flask`(服务器)。
- **异步高并发**:`asyncio`+`aiohttp`(IO密集型场景)。
实际开发中,需根据场景选择模块:简单TCP/UDP通信用`socket`,HTTP爬虫用`requests`,高并发API服务用`Flask+aiohttp`,数据传输优先用JSON序列化。掌握这些工具,可快速实现客户端、服务器及复杂网络应用。
# 十、MySQL
MySQL 是一款开源的关系型数据库管理系统(RDBMS),基于 SQL(结构化查询语言)实现数据存储、查询与管理,广泛应用于 Web 开发、企业系统等场景。本文从基础概念、SQL 语法、高级特性到性能优化,结合案例详细解析核心知识点。
## 一、基础概念与架构
### 1. 核心概念
- **数据库(Database)**:存储数据的仓库,MySQL 中以目录形式存在(如 `school_db`)。
- **表(Table)**:数据库中的数据集合,由行(记录)和列(字段)组成(如 `students` 表存储学生信息)。
- **数据类型**:定义列存储的数据格式,关键类型如下:
| 类型大类 | 常用类型 | 说明 |
| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| **数值型** | `INT`(整数)、`DECIMAL(10,2)`(小数,如价格) | `INT` 占4字节,`DECIMAL(10,2)` 精确到小数点后2位。 |
| **字符串型** | `VARCHAR(255)`(变长,如姓名)、`CHAR(10)`(定长,如身份证号) | `VARCHAR` 按需分配空间(节省内存),`CHAR` 固定长度(查询更快)。 |
| **日期时间型** | `DATETIME`(YYYY-MM-DD HH:MM:SS)、`TIMESTAMP`(时间戳,受时区影响) | `DATETIME` 范围大(1000-9999年),`TIMESTAMP` 范围小(1970-2038年)。 |
| **布尔型** | `BOOL`/`BOOLEAN`(等价于 `TINYINT(1)`,`1`=真,`0`=假) | 存储逻辑判断结果(如 `is_active` 字段标记用户是否激活)。 |
### 2. 数据库约束(Constraint)
约束用于保证数据完整性,常用约束如下:
- **主键(PRIMARY KEY)**:唯一标识表中记录,非空且唯一(一张表只能有一个主键,可组合多列)。
- **外键(FOREIGN KEY)**:关联两张表,确保子表记录在父表中存在(如 `scores.student_id` 关联 `students.id`)。
- **唯一(UNIQUE)**:列值唯一(允许 NULL,但 NULL 只允许出现一次)。
- **非空(NOT NULL)**:列值不允许为空(如 `students.name` 必须填写)。
- **检查(CHECK)**:限制列值范围(如 `scores.grade BETWEEN 0 AND 100`,MySQL 8.0+ 支持)。
### 3. MySQL 架构
- **连接层**:处理客户端连接(如 TCP 握手、身份验证)。
- **SQL 层**:解析 SQL、优化执行计划、事务管理、权限校验(核心层)。
- **存储引擎层**:负责数据的物理存储与读取,MySQL 支持多种引擎(如 `InnoDB`、`MyISAM`),默认使用 **InnoDB**(支持事务、外键、行级锁)。
## 二、SQL 核心语法
### 1. 数据库与表操作(DDL:数据定义语言)
#### 案例1:创建数据库与表
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建数据库(指定字符集为 utf8mb4,支持 emoji)
CREATE DATABASE IF NOT EXISTS school_db
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_general_ci;
-- 使用数据库
USE school_db;
-- 创建学生表(含主键、非空、唯一约束)
CREATE TABLE IF NOT EXISTS students (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增(AUTO_INCREMENT)
name VARCHAR(50) NOT NULL, -- 姓名,非空
email VARCHAR(100) UNIQUE, -- 邮箱,唯一(不允许重复)
age INT CHECK (age > 0 AND age < 150), -- 年龄范围检查(MySQL 8.0+)
created_at DATETIME DEFAULT CURRENT_TIMESTAMP -- 创建时间,默认当前时间
) ENGINE=InnoDB; -- 指定存储引擎为 InnoDB
-- 创建成绩表(含外键关联学生表)
CREATE TABLE IF NOT EXISTS scores (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course VARCHAR(50) NOT NULL,
grade DECIMAL(5,2) NOT NULL,
-- 外键约束:student_id 必须存在于 students.id 中
FOREIGN KEY (student_id) REFERENCES students(id)
ON DELETE CASCADE -- 级联删除:若学生记录删除,成绩记录自动删除
) ENGINE=InnoDB;
<div id="code-wrapper-a644dccd" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-a644dccd')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### 2. 数据操作(DML:数据操纵语言)
#### 案例2:插入、更新、删除数据
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 插入学生数据(INSERT)
INSERT INTO students (name, email, age)
VALUES
('张三', 'zhangsan@example.com', 20),
('李四', 'lisi@example.com', 22),
('王五', 'wangwu@example.com', 21);
-- 插入成绩数据(关联学生 ID)
INSERT INTO scores (student_id, course, grade)
VALUES
(1, '数学', 90.5),
(1, '英语', 85),
(2, '数学', 78),
(3, '英语', 92);
-- 更新数据(UPDATE):将张三的年龄改为 21
UPDATE students
SET age = 21
WHERE name = '张三'; -- 务必加 WHERE,否则全表更新!
-- 删除数据(DELETE):删除邮箱为 lisi@example.com 的学生(会触发外键级联删除成绩)
DELETE FROM students
WHERE email = 'lisi@example.com';
<div id="code-wrapper-6708dbb7" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-6708dbb7')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### 3. 数据查询(DQL:数据查询语言)
查询是 MySQL 核心,支持单表查询、多表连接、子查询、聚合分析等。
#### (1)单表查询:基础语法
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 语法:SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 排序 LIMIT 限制条数
-- 案例:查询年龄 >20 的学生,按年龄降序取前2名
SELECT id, name, age
FROM students
WHERE age > 20
ORDER BY age DESC
LIMIT 2;
-- 结果:
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 3 | 王五 | 21 | -- 张三年龄已更新为21,若 age>20 则包含
| 1 | 张三 | 21 |
+----+--------+-----+
<div id="code-wrapper-517da045" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-517da045')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### (2)多表连接:关联查询
**核心场景**:需从多张表中合并数据(如查询学生姓名+对应课程成绩)。
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 内连接(INNER JOIN):只返回两表匹配的记录
SELECT
s.name AS 学生姓名,
sc.course AS 课程,
sc.grade AS 成绩
FROM students s -- 别名 s
INNER JOIN scores sc -- 别名 sc
ON s.id = sc.student_id; -- 连接条件:学生 ID 关联
-- 结果(注意:李四已被删除,成绩也被级联删除,故不显示):
+----------+--------+------+
| 学生姓名 | 课程 | 成绩 |
+----------+--------+------+
| 张三 | 数学 | 90.5 |
| 张三 | 英语 | 85 |
| 王五 | 英语 | 92 |
+----------+--------+------+
-- 左连接(LEFT JOIN):返回左表所有记录,右表无匹配则显示 NULL
SELECT
s.name,
sc.course,
sc.grade
FROM students s
LEFT JOIN scores sc ON s.id = sc.student_id;
-- 结果(王五只有英语成绩,数学课程显示 NULL):
+----------+--------+------+
| name | course | grade|
+----------+--------+------+
| 张三 | 数学 | 90.5 |
| 张三 | 英语 | 85 |
| 王五 | 英语 | 92 |
| 王五 | NULL | NULL | -- 左表学生王五存在,右表无数学成绩
+----------+--------+------+
<div id="code-wrapper-ca28eee3" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-ca28eee3')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### (3)子查询:嵌套查询
子查询是嵌套在其他 SQL 中的查询,用于复杂条件过滤或数据计算。
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 案例1:查询数学成绩 > 80 的学生姓名(子查询获取符合条件的 student_id)
SELECT name
FROM students
WHERE id IN (
SELECT student_id
FROM scores
WHERE course = '数学' AND grade > 80
);
-- 结果:只有张三的数学成绩 90.5 >80
+--------+
| name |
+--------+
| 张三 |
+--------+
-- 案例2:查询平均分最高的学生(子查询计算每个学生的平均分,主查询取最大值)
SELECT s.name, AVG(sc.grade) AS 平均分
FROM students s
JOIN scores sc ON s.id = sc.student_id
GROUP BY s.id -- 按学生 ID 分组
HAVING 平均分 = ( -- HAVING 过滤分组后的结果(WHERE 用于行过滤)
SELECT MAX(avg_grade)
FROM (
SELECT AVG(grade) AS avg_grade
FROM scores
GROUP BY student_id
) AS temp -- 子查询需别名
);
-- 结果:张三平均分 (90.5+85)/2=87.75,王五平均分 92 → 王五最高
+--------+--------+
| name | 平均分 |
+--------+--------+
| 王五 | 92.00 |
+--------+--------+
<div id="code-wrapper-d3b335f1" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-d3b335f1')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### (4)聚合函数与分组查询
聚合函数对数据进行统计计算,需配合 `GROUP BY` 分组使用,常用函数:`COUNT()`(计数)、`SUM()`(求和)、`AVG()`(平均)、`MAX()`/`MIN()`(最大/小值)。
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 案例:统计每门课程的平均分、最高分、参与人数
SELECT
course AS 课程,
AVG(grade) AS 平均分,
MAX(grade) AS 最高分,
COUNT(student_id) AS 参与人数
FROM scores
GROUP BY course; -- 按课程分组
-- 结果:
+--------+--------+--------+----------+
| 课程 | 平均分 | 最高分 | 参与人数 |
+--------+--------+--------+----------+
| 数学 | 90.50 | 90.5 | 1 | -- 李四的数学成绩已被级联删除
| 英语 | 88.50 | 92 | 2 | -- 张三85 + 王五92,共2人
+--------+--------+--------+----------+
<div id="code-wrapper-b0c575a6" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-b0c575a6')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
## 二、高级特性
### 1. 索引(Index)
索引是优化查询速度的核心,通过构建“数据目录”减少磁盘 IO,类比书籍的目录。
#### (1)索引类型
| 索引类型 | 说明 | 适用场景 |
| ------------ | ------------------------------------------------ | ------------------------------------------------------------ |
| **主键索引** | 主键列自动创建,唯一且非空(`PRIMARY KEY`) | 按主键查询(如 `WHERE id=1`)。 |
| **唯一索引** | 列值唯一(`UNIQUE INDEX`) | 唯一字段查询(如 `WHERE email='xxx'`)。 |
| **普通索引** | 手动创建的非唯一索引(`INDEX`) | 频繁查询的非主键列(如 `WHERE course='数学'`)。 |
| **复合索引** | 多列组合索引(`INDEX idx_name_age (name, age)`) | 多条件查询(如 `WHERE name='张三' AND age=21`)。 |
| **全文索引** | 对文本内容分词索引(`FULLTEXT INDEX`) | 模糊查询长文本(如 `WHERE MATCH(content) AGAINST('MySQL')`)。 |
#### (2)案例:创建与使用索引
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建普通索引:对 scores 表的 course 列(频繁按课程查询)
CREATE INDEX idx_course ON scores(course);
-- 创建复合索引:对 students 表的 name 和 age 列(多条件查询)
CREATE INDEX idx_name_age ON students(name, age);
-- 查看索引:
SHOW INDEX FROM students; -- 显示 students 表所有索引
-- 使用索引查询:按课程查成绩(索引生效,查询速度快)
EXPLAIN SELECT * FROM scores WHERE course = '英语'; -- EXPLAIN 分析执行计划
-- 结果中 `type` 为 `ref`,`key` 为 `idx_course`,表示使用索引。
-- 索引失效场景:
-- 1. 函数操作:对索引列使用函数(如 `WHERE YEAR(created_at) = 2023`)
-- 2. 模糊查询前导 %:`WHERE name LIKE '%三'`(改为 `LIKE '张%'` 可生效)
-- 3. 复合索引不满足最左前缀:创建 `(name, age)` 后,单独查 `age=21` 不生效
<div id="code-wrapper-b2d27a05" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-b2d27a05')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### 2. 事务(Transaction)
事务是一组不可分割的 SQL 操作,要么全部执行成功,要么全部失败(如转账:A 扣款和 B 收款必须同时成功)。
#### (1)ACID 特性
- **原子性(Atomicity)**:事务中所有操作“要么全成功,要么全回滚”(如转账失败,A 扣款回滚)。
- **一致性(Consistency)**:事务执行前后数据状态合法(如转账后 A+B 总金额不变)。
- **隔离性(Isolation)**:多事务并发时,操作互不干扰(避免脏读、不可重复读、幻读)。
- **持久性(Durability)**:事务提交后,数据永久保存(即使数据库崩溃也不丢失)。
#### (2)案例:事务控制
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 开启事务(默认自动提交,需手动关闭)
SET autocommit = 0; -- 关闭自动提交
START TRANSACTION; -- 标记事务开始
-- 步骤1:A 账户扣款(假设账户表 `accounts`,id=1 为 A,id=2 为 B)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 步骤2:B 账户收款
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 检查是否成功(如无异常则提交,有异常则回滚)
COMMIT; -- 提交事务(数据永久生效)
-- ROLLBACK; -- 若步骤2失败,执行回滚(A 扣款撤销)
SET autocommit = 1; -- 恢复自动提交
<div id="code-wrapper-ef2ba08a" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-ef2ba08a')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### (3)事务隔离级别
MySQL 默认隔离级别为 **可重复读(REPEATABLE READ)**,解决脏读、不可重复读,通过 MVCC(多版本并发控制)实现。
| 隔离级别 | 脏读(读未提交数据) | 不可重复读(同一查询结果不一致) | 幻读(新增数据被读取) |
| ---------------------------- | -------------------- | -------------------------------- | ---------------------- |
| 读未提交(Read Uncommitted) | ✅ 允许 | ✅ 允许 | ✅ 允许 |
| 读已提交(Read Committed) | ❌ 禁止 | ✅ 允许 | ✅ 允许 |
| 可重复读(Repeatable Read) | ❌ 禁止 | ❌ 禁止 | ❌ 禁止(MySQL 特有) |
| 串行化(Serializable) | ❌ 禁止 | ❌ 禁止 | ❌ 禁止 |
### 3. 存储过程与函数
存储过程(Stored Procedure)和函数(Function)是预编译的 SQL 集合,存储在数据库中,可重复调用,减少网络传输开销。
#### (1)存储过程:无返回值限制
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建存储过程:根据学生 ID 查询所有成绩
DELIMITER $$ -- 临时修改分隔符(避免与 SQL 中的 ; 冲突)
CREATE PROCEDURE GetStudentScores(IN student_id INT) -- IN:输入参数
BEGIN
SELECT course, grade
FROM scores
WHERE scores.student_id = student_id;
END $$
DELIMITER ; -- 恢复分隔符
-- 调用存储过程:查询 ID=1 的学生成绩
CALL GetStudentScores(1);
-- 结果:
+--------+-------+
| course | grade |
+--------+-------+
| 数学 | 90.5 |
| 英语 | 85 |
+--------+-------+
<div id="code-wrapper-8d01cd83" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-8d01cd83')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### (2)函数:有且只有一个返回值
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建函数:计算课程平均分
DELIMITER $$
CREATE FUNCTION GetCourseAvg(course_name VARCHAR(50))
RETURNS DECIMAL(5,2) -- 返回值类型
DETERMINISTIC -- 相同输入返回相同结果(优化标记)
BEGIN
DECLARE avg_grade DECIMAL(5,2);
SELECT AVG(grade) INTO avg_grade
FROM scores
WHERE course = course_name;
RETURN avg_grade;
END $$
DELIMITER ;
-- 调用函数:查询英语平均分
SELECT GetCourseAvg('英语') AS 英语平均分;
-- 结果:
+----------+
| 英语平均分 |
+----------+
| 88.50 |
+----------+
<div id="code-wrapper-1bc89d44" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-1bc89d44')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### 4. 触发器(Trigger)
触发器是自动执行的 SQL 代码,当表发生 `INSERT`/`UPDATE`/`DELETE` 事件时触发,用于数据校验、日志记录等。
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建触发器:插入成绩时,若分数 >100 自动设为 100
DELIMITER $$
CREATE TRIGGER BeforeScoreInsert
BEFORE INSERT ON scores -- BEFORE:插入前触发
FOR EACH ROW -- 每一行触发一次
BEGIN
IF NEW.grade > 100 THEN -- NEW:新插入的行
SET NEW.grade = 100;
END IF;
END $$
DELIMITER ;
-- 测试触发器:插入分数 105 的记录
INSERT INTO scores (student_id, course, grade) VALUES (1, '语文', 105);
-- 结果:grade 被自动改为 100
SELECT * FROM scores WHERE course = '语文';
+----+------------+--------+-------+
| id | student_id | course | grade |
+----+------------+--------+-------+
| 5 | 1 | 语文 | 100 |
+----+------------+--------+-------+
<div id="code-wrapper-65197127" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-65197127')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### 5、游标(Cursor)
#### **一、游标的概念**
**游标(Cursor)** 是 MySQL 中用于**逐行处理查询结果集**的数据库对象。它允许用户在查询返回的结果集上进行**定位、读取和操作单行数据**,而非一次性加载整个结果集。
- **本质**:游标是结果集的“指针”,可类比编程语言中的“迭代器”(Iterator),支持按行遍历数据。
- 特性
:
- **只读性**:MySQL 游标默认只能读取数据,不能修改结果集(部分数据库支持可更新游标,MySQL 不支持)。
- **单向性**:只能从结果集第一行向后移动,无法回滚到前一行。
- **作用域**:仅在存储过程、函数或触发器中使用,不能在普通 SQL 语句中直接调用。
#### **二、游标的使用场景**
游标适用于**需要逐行处理数据**的场景,尤其是复杂逻辑判断或关联操作,常见场景包括:
1. **批量数据更新/删除**
当需要根据查询结果逐行更新或删除关联表数据时(如根据订单记录更新用户积分)。
2. **复杂计算与数据转换**
对结果集中的每行数据进行多步骤计算(如按规则生成报表、数据清洗)。
3. **大结果集处理**
当结果集过大(如百万级记录),一次性加载到内存会导致性能问题,游标可逐行读取,减少内存占用。
4. **多表关联逻辑处理**
需结合多行数据的条件判断,例如“当用户订单总金额超过 1000 时,升级会员等级”。
#### **三、游标的基本操作步骤**
MySQL 中游标的使用需遵循固定流程,核心步骤包括:**声明游标 → 打开游标 → 读取数据 → 关闭游标**,并需配合**异常处理**(处理结果集结束)
### **四、操作案例:用游标批量更新用户积分**
#### **场景需求**
假设有两张表:
- `orders`(订单表):存储用户订单,字段 `user_id`(用户 ID)、`amount`(订单金额)。
- `users`(用户表):存储用户信息,字段 `id`(用户 ID)、`total_spent`(总消费金额,需更新)。
**目标**:通过游标逐行读取 `orders` 表,累加每个用户的订单金额,更新到 `users` 表的 `total_spent` 字段。
#### **步骤 1:创建测试表与数据**
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
total_spent DECIMAL(10,2) DEFAULT 0 -- 总消费金额,初始为 0
);
-- 创建订单表
CREATE TABLE IF NOT EXISTS orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 插入测试数据
INSERT INTO users (username) VALUES ('张三'), ('李四'), ('王五');
INSERT INTO orders (user_id, amount) VALUES
(1, 200), (1, 300), -- 张三的两笔订单:200+300=500
(2, 150), -- 李四的一笔订单:150
(3, 400); -- 王五的一笔订单:400
<div id="code-wrapper-b3d2fda0" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-b3d2fda0')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### **步骤 2:创建存储过程,使用游标更新数据**
</code></pre><div class="code-expand-mask"></div></div></div>sql
DELIMITER $$ -- 修改分隔符(避免与 SQL 中的 ; 冲突)
CREATE PROCEDURE UpdateUserTotalSpent()
BEGIN
-- 声明变量:存储游标读取的数据和循环控制标志
DECLARE v_user_id INT; -- 订单表中的用户 ID
DECLARE v_amount DECIMAL(10,2); -- 订单金额
DECLARE done INT DEFAULT 0; -- 循环结束标志(0:未结束,1:结束)
-- 1. 声明游标:定义要处理的结果集(查询所有订单的 user_id 和 amount)
DECLARE order_cursor CURSOR FOR
SELECT user_id, amount FROM orders;
-- 2. 声明异常处理:当游标读取完所有数据时,设置 done=1
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 3. 打开游标:初始化结果集
OPEN order_cursor;
-- 4. 循环读取数据:使用 LOOP 循环逐行处理
read_loop: LOOP
-- 4.1 获取当前行数据到变量中
FETCH order_cursor INTO v_user_id, v_amount;
-- 4.2 判断是否读取完毕:若 done=1,退出循环
IF done = 1 THEN
LEAVE read_loop; -- 退出循环
END IF;
-- 4.3 业务逻辑:累加用户总消费金额(更新 users 表)
UPDATE users
SET total_spent = total_spent + v_amount -- 累加订单金额到总消费
WHERE id = v_user_id;
END LOOP read_loop;
-- 5. 关闭游标:释放资源
CLOSE order_cursor;
-- 提示执行完成
SELECT '用户总消费金额更新完成' AS result;
END $$
DELIMITER ; -- 恢复默认分隔符
<div id="code-wrapper-42e18b44" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-42e18b44')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### **步骤 3:调用存储过程并验证结果**
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 调用存储过程
CALL UpdateUserTotalSpent();
-- 查看更新后的用户表
SELECT id, username, total_spent FROM users;
<div id="code-wrapper-feb9ecca" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-feb9ecca')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
输出结果:
</code></pre><div class="code-expand-mask"></div></div></div>sql
+----+----------+-------------+
| id | username | total_spent |
+----+----------+-------------+
| 1 | 张三 | 500.00 | -- 200+300=500
| 2 | 李四 | 150.00 | -- 150
| 3 | 王五 | 400.00 | -- 400
+----+----------+-------------+
<div id="code-wrapper-a3e45852" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-a3e45852')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### **案例解析**
1. **游标声明**:`DECLARE order_cursor CURSOR FOR SELECT user_id, amount FROM orders;` 定义游标,关联订单表的查询结果集。
2. **异常处理**:`DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;` 当游标读取完所有行(`FETCH` 不到数据)时,触发 `NOT FOUND` 条件,设置 `done=1`,用于退出循环。
3. **循环读取**:通过 `LOOP` 循环和 `FETCH` 语句逐行读取订单数据,每次读取一行并更新 `users` 表中对应用户的 `total_spent`。
4. **资源释放**:`CLOSE order_cursor;` 关闭游标,释放结果集资源。
#### **五、游标使用注意事项**
**性能问题**:游标逐行处理数据效率较低,若能通过 SQL 批量操作(如 `UPDATE ... JOIN`)实现,优先使用 SQL 而非游标。例如上述案例可直接用关联更新:
</code></pre><div class="code-expand-mask"></div></div></div>sql
UPDATE users u
JOIN (SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id) o
ON u.id = o.user_id
SET u.total_spent = o.total;
<div id="code-wrapper-294b97ca" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-294b97ca')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
此 SQL 效率远高于游标,游标仅在 SQL 难以实现复杂逻辑时使用。
**内存占用**:即使游标逐行处理,结果集仍需存储在临时表中(MySQL 内部处理),若结果集过大,可能导致临时表溢出,需谨慎使用。
**事务控制**:游标操作在存储过程中默认在事务内,若中途出错,可通过 `ROLLBACK` 回滚所有更新,避免数据不一致。
**语法顺序**:存储过程中,变量声明(`DECLARE`)需按以下顺序:
1. 局部变量 → 2. 游标 → 3. 异常处理程序(`CONTINUE HANDLER`),否则会报错。
### **六、总结**
游标是 MySQL 中用于**逐行处理结果集**的工具,核心价值在于支持程序化数据处理,适用于复杂逻辑或大结果集场景。其使用步骤为 **声明→打开→读取→关闭**,需配合异常处理(`NOT FOUND` 条件)控制循环。
但需注意:**游标效率较低,优先使用 SQL 批量操作**,仅在 SQL 难以实现复杂业务逻辑时(如多步骤计算、条件分支)使用游标。掌握游标可提升存储过程的灵活性,是数据库开发中处理复杂数据逻辑的重要工具。
## 三、数据库设计:三大范式
数据库设计需遵循 **范式(Normal Form)**,避免数据冗余和异常(插入/更新/删除异常),核心为三大范式:
### 1. 1NF(第一范式):原子性
**要求**:列值不可再分(如“联系方式”拆分为“电话”和“邮箱”)。
**反例**:`students` 表中 `contact` 列存储“电话-邮箱”(如 `13800138000-alice@xxx.com`),需拆分为 `phone` 和 `email` 两列。
### 2. 2NF(第二范式):完全依赖
**要求**:非主键列必须完全依赖主键,不能依赖主键的一部分(适用于复合主键表)。
**反例**:`scores` 表若主键为 `(student_id, course)`,则 `teacher` 列依赖 `course`(部分依赖),需拆分出 `courses` 表(`course, teacher`),`scores` 表仅存 `student_id, course, grade`。
### 3. 3NF(第三范式):无传递依赖
**要求**:非主键列不依赖其他非主键列(即列之间无传递关系)。
**反例**:`students` 表中 `class_id`(班级 ID)和 `class_name`(班级名),`class_name` 依赖 `class_id`(传递依赖),需拆分出 `classes` 表(`class_id, class_name`),`students` 表仅存 `class_id`。
#### 案例:符合三范式的学生-课程-成绩系统
</code></pre><div class="code-expand-mask"></div></div></div>sql
students(id, name, email, age) -- 主键 id
courses(id, course_name, teacher) -- 主键 id
scores(id, student_id, course_id, grade) -- 主键 id,外键 student_id 关联 students.id,course_id 关联 courses.id
<div id="code-wrapper-6a44e46b" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-6a44e46b')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
## 四、性能优化核心策略
### 1. 慢查询优化
- **开启慢查询日志**:记录执行时间超过阈值的 SQL(默认 `long_query_time=10` 秒)。
</code></pre><div class="code-expand-mask"></div></div></div>sql
SET GLOBAL slow_query_log = ON; -- 开启慢查询日志
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log'; -- 日志路径
SET GLOBAL long_query_time = 1; -- 阈值设为 1 秒
<div id="code-wrapper-31cac5cd" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-31cac5cd')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
- **使用 `EXPLAIN` 分析 SQL**:查看执行计划,重点关注 `type`(访问类型,`ref`/`range` 优,`ALL` 全表扫描最差)、`key`(是否使用索引)、`rows`(扫描行数)。
### 2. 索引优化
- 优先在 **频繁查询的列**、**WHERE 条件列**、**JOIN 关联列** 上创建索引。
- 避免过度索引(索引会降低插入/更新速度,每个索引需维护)。
- 复合索引遵循 **最左前缀原则**(如 `(name, age)` 索引,`WHERE name='张三'` 生效,`WHERE age=21` 不生效)。
### 3. SQL 语句优化
- 避免 `SELECT *`(只查需要的列,减少 IO 和内存)。
- 用 `IN` 代替 `OR`(如 `WHERE id IN (1,2,3)` 优于 `WHERE id=1 OR id=2 OR id=3`)。
- 小表驱动大表(`JOIN` 时,小表放左表,减少循环次数)。
- 避免在索引列上使用函数(如 `WHERE YEAR(created_at)=2023` 改为 `WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31'`)。
## 五、pymysql
PyMySQL 是 Python 中用于连接和操作 MySQL 数据库的第三方库,纯 Python 实现,兼容 `MySQLdb` 接口,广泛应用于 Python 后端开发、数据爬虫、自动化脚本等场景。
### 一、安装与环境配置
#### 1. 安装 PyMySQL
通过 `pip` 快速安装(Python 3.4+ 支持):
</code></pre><div class="code-expand-mask"></div></div></div>bash
pip install pymysql
<div id="code-wrapper-27ad4f25" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-27ad4f25')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 2. 连接 MySQL 数据库
连接数据库需提供 MySQL 服务的 **连接参数**,核心参数如下:
| 参数 | 说明 | 示例值 |
| ------------ | ------------------------------------ | ----------------------- |
| `host` | MySQL 服务器地址 | `'localhost'` 或远程 IP |
| `port` | 端口号(默认 3306) | `3306` |
| `user` | 数据库用户名 | `'root'` |
| `password` | 用户密码 | `'123456'` |
| `database` | 要连接的数据库名(可选) | `'school_db'` |
| `charset` | 字符集(建议 `utf8mb4`,支持 emoji) | `'utf8mb4'` |
| `autocommit` | 是否自动提交事务(默认 `False`) | `True`/`False` |
基础代码连接
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
# 建立连接
conn = pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
database='school_db', # 连接后直接指定数据库(可选,也可后续用 USE 语句)
charset='utf8mb4' # 必须显式指定,否则中文可能乱码
)
# 关闭连接(使用后务必关闭,释放资源)
conn.close()
<div id="code-wrapper-adf78422" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-adf78422')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 3. 常见连接问题
- **中文乱码**:连接时未指定 `charset='utf8mb4'`,或数据库表字符集非 `utf8mb4`。
- **连接超时**:网络问题或 MySQL 服务未启动,可通过 `connect_timeout=10` 设置超时时间(单位秒)。
- **权限错误**:用户名/密码错误,或 MySQL 未授权远程连接(需执行 `GRANT ALL ON *.* TO 'user'@'%' IDENTIFIED BY 'password';` 开放权限)。
### 二、基本操作:增删改查(CRUD)
PyMySQL 通过 **游标(Cursor)** 执行 SQL 语句,核心流程为:**建立连接 → 创建游标 → 执行 SQL → 处理结果 → 关闭游标/连接**。
#### 1. 游标(Cursor):执行 SQL 的核心对象
游标用于执行 SQL 并获取结果,PyMySQL 提供两种常用游标:
- **普通游标(默认)**:返回结果为元组(`tuple`),需通过索引访问列值(如 `row[0]`)。
- **字典游标**:返回结果为字典(`dict`),可通过列名访问(如 `row['name']`),需在创建游标时指定 `cursorclass=pymysql.cursors.DictCursor`。
#### 2. 核心操作案例
##### (1)查询数据(SELECT)
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
# 1. 建立连接
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
try:
# 2. 创建字典游标(方便通过列名访问)
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
# 3. 执行 SQL 查询(查询 students 表中年龄 >20 的学生)
sql = "SELECT id, name, age FROM students WHERE age > %s"
# 注意:参数化查询用 %s 占位符(不要用 f-string 拼接 SQL,避免 SQL 注入!)
cursor.execute(sql, (20,)) # 参数用元组传入
# 4. 获取结果(fetchone() 单条,fetchall() 所有,fetchmany(n) 前 n 条)
result = cursor.fetchall() # 返回列表,元素为字典
print("查询结果:")
for row in result:
print(f"ID: {row['id']}, 姓名: {row['name']}, 年龄: {row['age']}")
finally:
# 5. 关闭连接(无论成功失败,确保连接关闭)
conn.close()
<div id="code-wrapper-853d7baa" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-853d7baa')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### (2)插入数据(INSERT)
插入后需手动提交事务(默认 `autocommit=False`),否则数据不会写入数据库。
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
try:
with conn.cursor() as cursor:
# 插入单条数据
sql = "INSERT INTO students (name, email, age) VALUES (%s, %s, %s)"
# 参数:姓名、邮箱、年龄
cursor.execute(sql, ('赵六', 'zhaoliu@example.com', 23))
# 提交事务(必须!否则插入不生效)
conn.commit()
print(f"插入成功,新增记录 ID: {cursor.lastrowid}") # 获取自增 ID
except Exception as e:
# 出错时回滚事务
conn.rollback()
print(f"插入失败:{e}")
finally:
conn.close()
<div id="code-wrapper-b67cc4b2" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-b67cc4b2')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### (3)更新数据(UPDATE)
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
try:
with conn.cursor() as cursor:
# 更新赵六的年龄为 24
sql = "UPDATE students SET age = %s WHERE name = %s"
cursor.execute(sql, (24, '赵六'))
# 提交事务
conn.commit()
print(f"更新成功,影响行数: {cursor.rowcount}") # cursor.rowcount 获取影响行数
except Exception as e:
conn.rollback()
print(f"更新失败:{e}")
finally:
conn.close()
<div id="code-wrapper-aa908624" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-aa908624')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### (4)删除数据(DELETE)
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
try:
with conn.cursor() as cursor:
# 删除邮箱为 zhaoliu@example.com 的学生
sql = "DELETE FROM students WHERE email = %s"
cursor.execute(sql, ('zhaoliu@example.com',))
conn.commit()
print(f"删除成功,影响行数: {cursor.rowcount}")
except Exception as e:
conn.rollback()
print(f"删除失败:{e}")
finally:
conn.close()
<div id="code-wrapper-7609bfb3" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-7609bfb3')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 3. 关键注意事项
- **参数化查询**:必须使用 `%s` 占位符传递参数(而非字符串拼接),防止 SQL 注入攻击。例如:
✅ 正确:`cursor.execute("SELECT * FROM users WHERE name = %s", (name,))`
❌ 错误:`cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")`(存在注入风险)。
- **事务提交**:默认 `autocommit=False`,增删改操作后需调用 `conn.commit()`,否则数据仅在事务内可见,未真正写入数据库。
- **资源释放**:使用 `with` 语句管理游标和连接(自动关闭),或手动调用 `cursor.close()` 和 `conn.close()`,避免连接泄露。
### 三、高级特性
#### 1. 批量操作(executemany)
对多条数据执行相同 SQL(如批量插入),使用 `executemany` 比循环单条插入效率更高(减少网络交互次数)。
**案例:批量插入学生数据**
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
try:
with conn.cursor() as cursor:
# 批量插入的 SQL
sql = "INSERT INTO students (name, email, age) VALUES (%s, %s, %s)"
# 数据列表:每个元素为一条记录的参数
data = [
('孙七', 'sunqi@example.com', 22),
('周八', 'zhouba@example.com', 21),
('吴九', 'wujin@example.com', 20)
]
# 批量执行
cursor.executemany(sql, data)
conn.commit()
print(f"批量插入成功,共插入 {cursor.rowcount} 条记录") # 输出:3
except Exception as e:
conn.rollback()
print(f"批量插入失败:{e}")
finally:
conn.close()
<div id="code-wrapper-64b01b88" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-64b01b88')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 2. 事务控制
PyMySQL 支持事务的 ACID 特性(原子性、一致性、隔离性、持久性),通过 `commit()` 提交事务,`rollback()` 回滚事务。
**案例:转账场景(确保原子性)**
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
def transfer_money(from_id, to_id, amount):
conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='bank_db',
charset='utf8mb4'
)
try:
with conn.cursor() as cursor:
# 1. 查询转出账户余额
cursor.execute("SELECT balance FROM accounts WHERE id = %s", (from_id,))
from_balance = cursor.fetchone()[0]
if from_balance < amount:
raise ValueError("余额不足,转账失败")
# 2. 转出账户扣款
cursor.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (amount, from_id))
# 3. 转入账户加款
cursor.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (amount, to_id))
# 所有操作成功,提交事务
conn.commit()
print("转账成功!")
except Exception as e:
# 任意步骤失败,回滚所有操作
conn.rollback()
print(f"转账失败:{e}")
finally:
conn.close()
# 调用:用户 1 向用户 2 转账 100 元
transfer_money(1, 2, 100)
<div id="code-wrapper-07af5af2" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-07af5af2')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 3. 连接池(DBUtils)
PyMySQL 本身不提供连接池,但可结合第三方库 `DBUtils` 实现连接池(复用连接,减少频繁创建/关闭连接的开销,提升高并发性能)。
##### (1)安装 DBUtils
</code></pre><div class="code-expand-mask"></div></div></div>bash
pip install dbutils
<div id="code-wrapper-670d4931" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-670d4931')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
##### (2)创建连接池并使用
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
from dbutils.pooled_db import PooledDB
# 创建连接池(参数与 pymysql.connect 一致,额外增加池配置)
pool = PooledDB(
creator=pymysql, # 使用 PyMySQL 作为连接创建器
maxconnections=10, # 最大连接数
mincached=2, # 初始化时池中的空闲连接数
maxcached=5, # 池中空闲连接的最大数量
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
# 从池获取连接(无需手动创建连接)
conn = pool.connection()
try:
with conn.cursor() as cursor:
cursor.execute("SELECT name FROM students LIMIT 1")
print(cursor.fetchone()) # 输出:('张三',)
finally:
conn.close() # 归还连接到池(非真正关闭)
<div id="code-wrapper-43c5497c" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-43c5497c')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 4. 上下文管理器(with 语句)
通过 `with` 语句管理连接和游标,自动处理资源释放,简化代码:
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
# 连接上下文管理器
with pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
) as conn:
# 游标上下文管理器
with conn.cursor(pymysql.cursors.DictCursor) as cursor:
cursor.execute("SELECT id, name FROM students")
print(cursor.fetchall()) # 自动关闭游标
# 连接自动关闭(无需手动调用 conn.close())
<div id="code-wrapper-f53a1361" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-f53a1361')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
### 四、综合案例:学生成绩管理系统(命令行版)
实现一个简单的学生成绩管理系统,支持添加学生、录入成绩、查询成绩功能,展示 PyMySQL 的综合应用。
#### 1. 数据库表结构
</code></pre><div class="code-expand-mask"></div></div></div>sql
-- 创建 students 表(学生信息)
CREATE TABLE IF NOT EXISTS students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT NOT NULL,
email VARCHAR(100) UNIQUE
);
-- 创建 scores 表(成绩信息)
CREATE TABLE IF NOT EXISTS scores (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course VARCHAR(50) NOT NULL,
grade DECIMAL(5,2) NOT NULL,
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
);
<div id="code-wrapper-cf408c3e" class="code-wrapper mb-6 rounded-lg overflow-hidden shadow-sm border border-gray-200"><div class="code-header bg-gray-800 px-4 py-2 flex items-center justify-between text-white text-sm font-mono select-none sticky top-0 z-20"><div class="flex items-center space-x-2"><span class="lang-indicator bg-blue-500 text-white px-2 py-1 rounded text-xs font-medium">PLAINTEXT</span><span class="filename text-gray-300 text-xs"></span></div><div class="flex items-center space-x-2"><button class="toggle-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="toggleCodeExpansion('code-wrapper-cf408c3e')" title="展开/收起代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path></svg></button><button class="copy-btn text-gray-400 hover:text-white transition-colors text-sm" onclick="copyCode(this)" title="复制代码"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg></button></div></div><div class="code-content relative"><pre class="line-numbers !m-0 !border-0 !rounded-none !bg-transparent max-h-96 overflow-y-auto relative"><code class="language-plaintext !font-mono !text-sm">
#### 2. Python 代码实现
</code></pre><div class="code-expand-mask"></div></div></div>python
import pymysql
from pymysql.cursors import DictCursor
class StudentScoreSystem:
def __init__(self):
# 初始化数据库连接
self.conn = pymysql.connect(
host='localhost',
user='root',
password='123456',
database='school_db',
charset='utf8mb4'
)
def add_student(self, name, age, email):
"""添加学生"""
try:
with self.conn.cursor() as cursor:
sql = "INSERT INTO students (name, age, email) VALUES (%s, %s, %s)"
cursor.execute(sql, (name, age, email))
self.conn.commit()
print(f"学生 {name} 添加成功,ID: {cursor.lastrowid}")
except Exception as e:
self.conn.rollback()
print(f"添加失败:{e}")
def add_score(self, student_id, course, grade):
"""录入成绩"""
try:
with self.conn.cursor() as cursor:
# 先检查学生是否存在
cursor.execute("SELECT id FROM students WHERE id = %s", (student_id,))
if not cursor.fetchone():
raise ValueError(f"学生 ID {student_id} 不存在")
# 插入成绩
sql = "INSERT INTO scores (student_id, course, grade) VALUES (%s, %s, %s)"
cursor.execute(sql, (student_id, course, grade))
self.conn.commit()
print(f"成绩录入成功:学生 {student_id},{course} {grade} 分")
except Exception as e:
self.conn.rollback()
print(f"录入失败:{e}")
def query_student_scores(self, student_id):
"""查询学生所有成绩"""
try:
with self.conn.cursor(DictCursor) as cursor:
sql = """
SELECT s.course, s.grade, st.name
FROM scores s
JOIN students st ON s.student_id = st.id
WHERE s.student_id = %s
"""
cursor.execute(sql, (student_id,))
results = cursor.fetchall()
if not results:
print(f"学生 ID {student_id} 暂无成绩记录")
return
print(f"\n学生 {results[0]['name']} 的成绩:")
for row in results:
print(f"{row['course']}: {row['grade']} 分")
except Exception as e:
print(f"查询失败:{e}")
def close(self):
"""关闭数据库连接"""
self.conn.close()
# 测试系统功能
if __name__ == "__main__":
system = StudentScoreSystem()
# 添加学生
system.add_student("张三", 20, "zhangsan@example.com")
# 录入成绩
system.add_score(1, "数学", 90.5)
system.add_score(1, "英语", 85)
# 查询成绩
system.query_student_scores(1)
# 关闭连接
system.close()
五、总结
PyMySQL 是 Python 操作 MySQL 的核心库,核心知识点包括:
- 安装配置:
pip install pymysql,通过pymysql.connect()建立连接,需指定主机、用户、密码等参数。 - 基本操作:通过游标执行 SQL,支持查询(
fetchone/fetchall)、增删改(需commit),参数化查询防注入。 - 高级特性:批量操作(
executemany)、事务控制(commit/rollback)、连接池(结合DBUtils)、上下文管理器(with语句)。 - 应用场景:Web 后端数据交互、数据爬虫存储、自动化脚本、数据分析等。
掌握 PyMySQL 可高效实现 Python 与 MySQL 的数据交互,是后端开发和数据处理的必备技能。实际使用中需注意事务一致性、连接资源释放及 SQL 注入防护,确保系统安全稳定。
六、总结
MySQL 核心知识点涵盖 基础概念(数据库、表、约束)、SQL 语法(DDL/DML/DQL)、高级特性(索引、事务、存储过程)、数据库设计(范式) 及 性能优化。实际应用中,需结合业务场景设计表结构,通过索引和 SQL 优化提升查询效率,利用事务保证数据一致性,最终实现高效、稳定的数据库系统。