# python高级编程 **Repository Path**: wang-tongweii/advanced-python-programming ## Basic Information - **Project Name**: python高级编程 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-21 - **Last Updated**: 2026-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 面向对象和面向过程 * 编程思想 > 就是人们利用计算机来解决问题的思维. * 分类 * 面向过程 > 它是一种编程思想, 强调的是以 步骤(过程) 为基础完成各种操作. * 面向对象 > **参考思路:** 概述, 思想特点, 举例, 总结 > > 它是一种编程思想, 强调的是以 **对象** 为基础完成各种操作, 它是基于 面向过程的. > 说到面向对象, 不得不提的就是它的三大思想特点: > > 1. 更符合人们的思考习惯. > 2. 把复杂的事情简单化. > 3. 把人们(程序员)从执行者变成指挥者. > > 举例: 越符合当时的场景越好, 例如: 买电脑, 洗衣服... > > 总结: 万物接对象. ## 面向对象特征介绍 * 三大特征 * 封装 * 继承 * 多态 * 封装简介 * 概述 > 就是隐藏对象的属性和实现细节, 仅对外提供公共的访问方式. * 举例 * 电脑, 手机, 函数, 类 = 属性 + 行为 * 好处 > 提高代码的安全性. (私有化) > > 提高代码的复用性. (函数) * 继承 * 概述 > 子类继承父类的成员, 例如: 属性, 行为等. > 大白话: 子承父业. * 好处 > 提高代码的复用性. * 多态 * 概述 > 大白话: 同一个事物在不同时刻表现出来的不同状态, 形态. > > 专业版: 同1个函数, 接收不同的对象, 有不同的效果。 ## 入门案例_汽车类 ```python """ 案例: 演示定义汽车类 及 使用类中的成员. 面向对象核心概念: 类: 抽象的概念, 看不见, 摸不着, 是 属性(名词) 和 行为(动词)的集合. 对象: 类的具体体现, 实现. 属性(名词): 用来描述事物的外在特征的, 例如: 姓名, 年龄... 格式: 和以前定义变量一样. 行为(动词): 用来描述事物能够做什么的, 例如: 吃, 喝... 格式: 和以前定义函数一样. 定义类的格式: class 类名: # 属性 # 行为 如何访问类中的成员? step1: 创建该类的对象. 对象名 = 类名() step2: 通过 对象名. 的方式调用. 对象性.属性名 对象名.行为名() 需求: 定义汽车类, 有跑的行为. """ # 1.定义汽车类. class Car: # 类名遵循 大驼峰命名法. # 属性 # 行为 def run(self): print('汽车会跑!...') # 2.创建汽车类的对象. c1 = Car() # 3. 调用Car类的run()函数, 简写版: 调用Car#run() c1.run() ``` ## self关键字介绍 * 案例1 ```python """ 案例: self关键字介绍. self介绍: 概述: 它是Python内置的关键字, 用于表示 本类当前对象的引用. 作用: 1个类是可以有多个对象的, 这多个对象都可以通过 对象名. 的方式访问类中的行为(函数) 函数默认有self属性, 函数通过self来区分到底是哪个对象调用的该函数. 大白话: 谁调用函数, self就代表哪个对象. """ # 需求: 定义汽车类, 创建多个该类的对象, 看看打印结果. # 1. 定义汽车类. class Car: # 属性 # 行为, 跑 def run(self): print('汽车会跑!...') print(f'我是run函数, self的值是: {self}') # 2.创建汽车类的对象. c1 = Car() print(f'c1对象: {c1}') # 通过 对象名. 的形式, 调用Car#run() c1.run() print('-' * 34) # 3.继续创建汽车类的对象. c2 = Car() print(f'c2对象: {c2}') # 通过 对象名. 的形式, 调用Car#run() c2.run() ``` * 案例2 ```python """ 案例: 演示通过 self关键字实现 在类内访问其它函数.' self关键字: 概述: 代表本类当前对象的引用, 谁(哪个对象)调用, self就代表谁. 作用: 用于实现函数 区分 不同对象的. 总结: 1.在 类外 访问类中的行为, 需要通过 对象名. 的方式访问. 2.在 类内 访问类中的行为,需要通过 self. 的方式访问。 """ # 需求: 定义汽车类, 类内有run()函数, 并在work()中调用run()函数, 创建该类对象, 调用上述的函数. # 1. 定义汽车类. class Car: # 属性(名词) # 行为(动词) # 1.1 run()函数 def run(self): print(f'{self} 汽车在跑...') # 1.2 work()函数, 在其内部调用run() def work(self): print(f'我是work函数, 我的self值: {self}') self.run() # self = 本类当前对象的引用. # 2.在类外访问Car类的行为(函数) c1 = Car() print(f'c1对象: {c1}') c1.run() # c1在跑 print('-' * 34) c1.work() # c1在work, c1在跑 print('=' * 34) # 分割线 # 3.再次创建对象. c2 = Car() print(f'c2对象: {c2}') c2.run() print('-' * 34) c2.work() ``` ## 入门案例_手机类 ```python """ 案例: 定义手机类, 能开机, 关机, 拍照. 回顾: 定义类的格式 class 类名: # 属性 # 行为 访问 类中成员 的格式: 类外: 对象名. 的方式 类内: self. 的方式 """ # 1.定义手机类. class Phone: # 属性 # 行为 # 1.1 开机. def open(self): print(f'{self} 手机开机了') # 1.2 关机. def close(self): print(f'{self} 手机关机了') # 1.3 拍照. def take_photo(self): print(f'{self} 手机拍照了') # 2. 创建手机类对象, 访问其成员. p1 = Phone() print(f'p1对象: {p1}') p1.open() p1.take_photo() p1.close() print('-' * 34) # 3.继续创建手机类对象, 访问其成员. p2 = Phone() print(f'p2对象: {p2}') p2.open() p2.take_photo() p2.close() ``` ## 类外_获取和设置对象的属性 ![1741853421574](assets/1741853421574.png) ```python """ 案例: 演示在类外 如何获取 和 设置 对象的属性. 类外, 设置对象的属性, 格式如下: 对象名.属性名 = 属性值 特点: 该属性独属于这个对象, 即: 该类的其它对象没有这个属性. 类外, 获取对象的属性, 格式如下: 对象名.属性名 """ # 需求: 创建汽车类, 设置为红色, 4个轮胎, 有跑的功能. # 1.创建汽车类. class Car: # 属性(名词), 事物具有哪些特征 -> 变量. # 行为(动词), 事物能够做什么 -> 函数. def run(self): print('汽车会跑...') # pass # 2.创建该类的对象 -> 这个是 类外 的位置. c1 = Car() c1.run() # 汽车会跑... # 细节1: 给c1对象设置属性. c1.color = '红色' c1.number = 4 # 细节2: 打印c1对象的属性值. print(f'颜色: {c1.color}, 轮胎数: {c1.number}') print('-' * 34) # 3.继续创建该类的对象. c2 = Car() c2.run() # 细节3: 尝试调用c2对象的 color和number属性 # print(f'颜色: {c2.color}, 轮胎数: {c2.number}') ``` ## 类内_获取对象的属性 ```python """ 案例: 演示类内如何获取对象的属性. 回顾(总结): 1. 类外访问类中的成员, 可以通过 对象名. 的方式. 2. 类内访问类中的成员, 可以通过 self. 的方式. 3. 类外通过 对象名.属性名 = 属性值 的方式 设置属性, 只有当前对象有. 细节: 类内如何设置属性, 要结合 魔法方法 __init__() 来实现, 稍后讲. """ # 1. 定义汽车类, 创建该类对象, 赋予颜色 和 轮胎数两个属性, 并在类内访问该属性. class Car: # 属性 # 行为 # 1.1 跑 def run(self): print('汽车会跑') # 1.2 定义函数show(), 实现 在类内访问 汽车对象的属性. def show(self): print(f'我是show函数, 对象的颜色: {self.color}, 轮胎数: {self.number}') # 2.创建汽车类的对象 c1 = Car() # 3. 给其(c1)赋予 属性 -> 类外设置属性. c1.color = '红色' c1.number = 4 # 4. 类外访问属性. print(f"颜色: {c1.color}, 轮胎数: {c1.number}") # 5. 类外访问行为(类中的函数) c1.run() c1.show() print('-' * 34) # 6. 继续创建汽车类对象, 尝试分别调用run(), show()函数. c2 = Car() c2.run() # c2.show() # 报错. ``` ## 魔法方法之init方法 * 图解 ![1741853389504](assets/1741853389504.png) * 案例1: 无参数版 ```python """ 案例: 演示 init魔法方法的 用法. 魔法方法: 概述/特点: Python内置的函数, 在满足特定的场景下, 会被 自动调用. 常用的魔法方法: __init__() 在(每次)创建对象的时候, 会自动触发该类的 __init__()函数. __str__() __del__() """ # 需求: 定义汽车类, 默认属性为: color='黑色', number=3 # 1. 定义汽车类. class Car: # 1.1 在魔法方法 init()中, 初始化: 属性. def __init__(self): print('我是 无参 init 魔法方法') # 1.2 在init魔法方法中, 初始化属性, 则: 该类所有的对象, 一创建, 就有这些属性了. self.color = '黑色' self.number = 3 # 1.3 定义show()函数, 打印该类对象的 各个属性值. def show(self): print(f'颜色: {self.color}, 轮胎数: {self.number}') # 2.创建汽车类对象. c1 = Car() # 会自动调用 __init__()函数. # 修改c1的属性值 c1.color = '红色' c1.number = 6 # 打印c1对象的属性值. print(c1.color, c1.number) c1.show() print('-' * 34) c2 = Car() c2.show() ``` * 案例2: 有参数版 ```python """ 案例: 演示魔法方法之 init 有参版, 实际开发常用. 回顾: __init__()魔法方法, 在创建对象的时候, 会被自动调用, 一般用于给该类对象 的属性进行初始化. 大白话举例: 无参版 init -> 默认上的有底色, 你需要重新涂色(覆盖底色) 有参版 init -> 默认没有涂色的石膏娃娃, 我们根据喜好自由涂色即可. """ # 需求: 创建汽车类, 不给默认值, 由汽车对象 外部各自赋值即可. # 1. 定义汽车类. class Car: # 2.有参的 __init__()函数, 参数值由: 外部对象自行赋值. def __init__(self, color, number): """ 该魔法方法用于给 汽车类 对象的属性 赋值. :param color: 车的颜色 :param number: 车的轮胎数 """ self.color = color self.number = number # 定义show()函数, 打印该类对象的 各个属性值. def show(self): print(f'颜色: {self.color}, 轮胎数: {self.number}') # 3. 创建汽车类对象. # c1 = Car() # 报错, 因为默认调用了init()函数, 但是该函数有参数, 则必须传参. c1 = Car('红色', 6) c1.show() print('-' * 23) c2 = Car('绿色', 4) c2.show() c3 = Car() ``` ## 魔法方法之str方法 ```python """ 案例: 演示 str魔法方法的 用法. 魔法方法: 概述/特点: Python内置的函数, 在满足特定的场景下, 会被 自动调用. 常用的魔法方法: __init__() 在(每次)创建对象的时候, 会自动触发该类的 __init__()函数. __str__() 当用print()函数 打印对象的时候, 会自动调用该对象(所在类)的 str魔法方法. 该魔法方法默认打印的是对象的地址值, 无意义, 一般都会重写, 改为打印 对象的各个属性值. __del__() """ # 1. 定义汽车类. class Car: # 2.有参的 __init__()函数, 参数值由: 外部对象自行赋值. def __init__(self, color, number): """ 该魔法方法用于给 汽车类 对象的属性 赋值. :param color: 车的颜色 :param number: 车的轮胎数 """ self.color = color self.number = number # 魔法方法str(), 默认打印地址值, 无意义, 一般会重写, 改为打印对象的各个属性值. def __str__(self): return f'颜色: {self.color}, 轮胎数: {self.number}' # return f'{self.color}, {self.number}' # 3.创建该类的对象. c1 = Car('绿色', 4) print(c1) # 输出语句打印对象, 默认调用了该对象 所在类的 str魔法方法. print('-' * 23) c2 = Car('红色', 6) print(c2) ``` ## 魔法方法之del方法 ```python """ 案例: 演示 str魔法方法的 用法. 魔法方法: 概述/特点: Python内置的函数, 在满足特定的场景下, 会被 自动调用. 常用的魔法方法: __init__() 在(每次)创建对象的时候, 会自动触发该类的 __init__()函数. __str__() 当用print()函数 打印对象的时候, 会自动调用该对象(所在类)的 str魔法方法. 该魔法方法默认打印的是对象的地址值, 无意义, 一般都会重写, 改为打印 对象的各个属性值. __del__() 当.py文件执行结束, 或者 手动 del 释放对象资源, 会自动调用该函数. """ # 1. 定义汽车类, 属性: 品牌. 行为:run() 通过del魔法方法删除该类的对象, 看看效果. class Car: # 2. 在魔法方法init中, 完成: 属性的初始化. def __init__(self, brand): self.brand = brand # 3.重写 str魔法方法, 打印对象的属性值. def __str__(self): return f'品牌: {self.brand}' # 4. 重写 del魔法方法, 删除对象时给出提示. def __del__(self): print(f'{self} 对象被删除了!') # 5. 创建汽车类对象. c1 = Car('小米 Su7 Ultra') print(c1) # 6. 手动访问 brand 属性. print(c1.brand) print('-' * 23) # 7.手动删除c1对象, 然后尝试 打印该对象 或者 访问对象的属性. # del c1 # print(c1) # 报错. print('程序结束!') ``` ## 减肥案例 ```python """ 案例: 减肥案例. 需求: 例如,小明同学当前体重是100kg。每当他跑步一次时,则会减少0.5kg;每当他大吃大喝一次时,则会增加2kg。请试着采用面向对象方式完成案例。 分析: 类名: Student 对象名: xm 属性(名词): 当前体重, current_weight 行为(动词) 跑步, 吃饭 """ # 1.定义学生类. class Student: # 2.在魔法方法init中, 完成: 对象的属性的初始化. def __init__(self): self.current_weight = 100 # 3.每当他跑步一次时,则会减少0.5kg def run(self): print('疯狂跑步...') self.current_weight -= 0.5 # 体重减小. # 4.大吃大喝. def eat(self): print('大吃大喝一顿...') self.current_weight += 2 # 5.重写魔法方法str, 打印属性值, 即: 当前体重. def __str__(self): # return '当前体重: %s' % self.current_weight return f'当前体重: {self.current_weight} kg!' # 6. 测试. if __name__ == '__main__': # 6.1 创建学生对象. xm = Student() # 6.2 跑步 xm.run() xm.run() # 6.3 吃喝 xm.eat() # 6.4 当前体重. print(xm) ``` ## 烤地瓜案例 ![1741857757308](assets/1741857757308.png) ```python """ 案例: 烤地瓜案例. 需求: 1. 定义地瓜类 -> SweetPotato 2. 属性: 被烤时间cook_time, 烘焙状态 cook_state, 调料 condiments 3. 行为: 烘烤cook(), 添加调料 add_condiment() 4. 魔法方法: init() -> 初始化属性, str() -> 打印地瓜信息. 5. 规则: 烘烤时间 地瓜状态 [0, 3) 生的 包左不包右, 前闭后开. [3, 7) 半生不熟 [7, 12) 熟了 [12, ∞] 糊了 """ # 1. 定义地瓜类 -> SweetPotato class SweetPotato: # 2. 在魔法方法__init__()中, 初始化地瓜的属性. def __init__(self): self.cook_time = 0 self.cook_state = '生的' self.condiments = [] # 3.具体的烘烤动作. def cook(self, time): # 3.1 根据烘烤时间, 修改地瓜的烘烤状态. if time < 0: print('无效值!') else: # 3.2 修改地瓜的 烘烤时间. self.cook_time += time # 3.3 根据烘烤时间, 修改地瓜的烘烤状态. if 0 <= self.cook_time < 3: self.cook_state = '生的' elif 3 <= self.cook_time < 7: self.cook_state = '半生不熟' elif 7 <= self.cook_time < 12: self.cook_state = '熟了' else: self.cook_state = '糊了' # 4. 添加调料 add_condiment() def add_condiment(self, condiment): self.condiments.append(condiment) # 5. 重写str()方法, 打印地瓜信息. def __str__(self): return f'烘烤时间: {self.cook_time}, 地瓜状态: {self.cook_state}, 调料: {self.condiments}' # 6.测试. if __name__ == '__main__': # 7. 创建地瓜对象 dg = SweetPotato() # 8. 具体的烘烤动作. # dg.cook(-3) dg.cook(3) dg.cook(5) dg.cook(7) # 9. 添加调料 dg.add_condiment('芥末/辣根') dg.add_condiment('折耳根') dg.add_condiment('豆汁') dg.add_condiment('鲱鱼罐头') # 10. 打印地瓜状态. print(dg) ``` ## 创建类的格式 ```python """ 案例: 创建类的格式介绍. 格式1: class 类名: pass 格式2: class 类名(): pass 格式3: # class 类名(父类名): class 类名(object): pass """ # 需求: 定义老师类 # class Teacher: # class Teacher(): class Teacher(object): # object是所有类的父类, Python中所有的类都直接或者间接继承自object类. pass t1 = Teacher() print(t1) ``` ## 继承入门 ```python """ 案例: 继承入门. 继承介绍: 概述: 大白话: 子承父业. 专业版: 子类可以继承父类的属性 和 行为. 写法: class 子类名(父类名): pass 例如: class A(B): pass 叫法: A: 子类, 派生类 B: 父类, 基类, 超类 好处: 提高代码的复用性 弊端: 耦合性增强了, 父类不好的内容, 子类想没有都不行. 扩展: 开发原则 高内聚, 低耦合. 内聚: 指的是类自己独立处理问题的能力. 耦合: 指的是类与类之间的关系. 大白话解释: 自己能搞定的事儿, 就不要麻烦别人. """ # 需求: 定义父类(男, 散步), 定义子类, 继承父类. # 1. 定义父类. class Father(object): def __init__(self): self.gender = '男' def walk(self): print('饭后走一走, 活到九十九!') # def smoking(self): # print('抽烟有害, 健康!') # 2. 定义子类. class Son(Father): pass # 3.测试子类的功能. s = Son() print(f'性别: {s.gender}') # 子类从父类继承过来 属性. s.walk() # 子类从父类继承过来 行为. # s.smoking() ``` ## 单继承演示 ```python """ 案例: 演示单继承, 即: 1个子类继承自 1个父类. 故事1: 一个摊煎饼的老师傅,在煎饼果子界摸爬滚打多年,研发了一套精湛的摊煎饼技术, 师父要把这套技术传授给他的唯一的最得意的徒弟。 分析: 1. 定义师傅类, Master 属性: kongfu 行为: make_cake() 2. 定义子类, Prentice, 继承师傅类. """ # 1. 定义师傅类. class Master: # 1.1 定义属性. def __init__(self): self.kongfu = '[古法配方]' # 1.2 定义行为. def make_cake(self): print(f'采用 {self.kongfu} 摊煎饼果子.') # 2.定义徒弟类, 继承自师傅类. class Prentice(Master): pass # 3.测试. p = Prentice() p.make_cake() ``` ## 多继承演示 ```python """ 案例: 演示多继承. 需求: 小明是个爱学习的好孩子,想学习更多的摊煎饼果子技术,于是,在百度搜索到黑马程序员学校,报班来培训学习摊煎饼果子技术。 扩展: MRO机制. 解释: Python中有MRO机制, 可以查看某个对象, 在调用函数时的 顺序, 即: 先找哪个类, 后找哪个类. 格式: 类名.mro() 类名.__mro__ """ # 1. 定义师傅类. class Master: # 1.1 定义师傅类属性. def __init__(self): self.kongfu = '[古法煎饼果子配方]' # 1.2 定义师傅类方法. def make_cake(self): print(f'运用 {self.kongfu} 制作煎饼果子') # 2. 定义黑马学校类. class School: # 2.1 定义学校类属性. def __init__(self): self.kongfu = '[黑马AI煎饼果子配方]' # 2.2 定义学校类方法. def make_cake(self): print(f'运用 {self.kongfu} 制作煎饼果子') # 3.定义徒弟类 -> 有个对象叫 小明. class Prentice(School, Master): # 从左往右, 就近原则. pass # 4.测试. xm = Prentice() print(xm.kongfu) # xm.make_cake() print('-' * 23) # 5. 查看mro机制的结果. print(Prentice.mro()) # Prentice -> School -> Master -> object print(Prentice.__mro__) # Prentice -> School -> Master -> object ``` ## 子类重写父类的功能 ```python """ 案例: 演示子类重写父类功能. 重写解释: 概述: 重写也叫覆盖, 即: 子类出现和父类重名的属性 或者 行为, 称之为: 重写. 调用层次: 遵循 就近原则, 子类有就用, 没有就去就近的父类找, 依次查找其所有的父类, 有就用, 没有就报错. """ # 故事3: 小明掌握了老师傅和黑马的技术后,自己潜心钻研出一套自己的独门配方的全新摊煎饼果子技术。 # 1. 老师父类. class Master: # 1.1 属性 def __init__(self): self.kongfu = '[古法煎饼果子配方]' # 1.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 2. 黑马学校类 class School: # 2.1 属性 def __init__(self): self.kongfu = '[黑马AI煎饼果子配方]' # 2.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3. 徒弟类 class Prentice(School, Master): # 3.1 属性 def __init__(self): self.kongfu = '[独创煎饼果子配方]' # 3.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 4. 测试. if __name__ == '__main__': # 4.1 创建徒弟类对象. p = Prentice() # 4.2 访问属性. print(p.kongfu) # 4.3 调用函数. p.make_cake() ``` ## 子类访问父类功能 * 方式1: **父类名.父类功能名(self)** ![1741916481470](assets/1741916481470.png) ```python """ 案例: 子类重写父类功能后, 继续访问父类功能. 思路: 1. 父类名.父类函数名(self) 精准访问, 想找哪个父类, 就调哪个父类. 2. super().父类函数名() 只能访问最近的那个父类, 有就用, 没有就往后继续查找. """ # 故事4: 很多顾客都希望能吃到徒弟做出的有自己独立品牌的煎饼果子,也有黑马配方技术的煎饼果子味道。 # 1. 老师父类. class Master: # 1.1 属性 def __init__(self): self.kongfu = '[古法煎饼果子配方]' # 1.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 2. 黑马学校类 class School: # 2.1 属性 def __init__(self): self.kongfu = '[黑马AI煎饼果子配方]' # 2.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3. 徒弟类 class Prentice(School, Master): # 3.1 属性 def __init__(self): self.kongfu = '[独创煎饼果子配方]' # 3.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3.3 调用父类的功能. def make_master_cake(self): Master.__init__(self) Master.make_cake(self) def make_school_cake(self): School.__init__(self) School.make_cake(self) # 4. 测试. if __name__ == '__main__': # 4.1 创建徒弟类对象. p = Prentice() # 4.2 访问属性. print(p.kongfu) # 独创 # 4.3 调用函数. p.make_cake() # 独创 p.make_master_cake() # 古法 p.make_school_cake() # AI print('-' * 34) p.make_cake() # AI ``` * 方式2: **super().父类功能名()** ```python """ 案例: 子类重写父类功能后, 继续访问父类功能. 思路: 1. 父类名.父类函数名(self) 精准访问, 想找哪个父类, 就调哪个父类. 2. super().父类函数名() 只能访问最近的那个父类, 有就用, 没有就往后继续查找. """ # 故事4: 很多顾客都希望能吃到徒弟做出的有自己独立品牌的煎饼果子,也有黑马配方技术的煎饼果子味道。 # 1. 老师父类. class Master: # 1.1 属性 def __init__(self): self.kongfu = '[古法煎饼果子配方]' # 1.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 2. 黑马学校类 class School: # 2.1 属性 def __init__(self): self.kongfu = '[黑马AI煎饼果子配方]' # 2.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3. 徒弟类 class Prentice(School, Master): # 3.1 属性 def __init__(self): self.kongfu = '[独创煎饼果子配方]' # 3.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3.3 调用父类的功能. # def make_master_cake(self): # Master.__init__(self) # Master.make_cake(self) # # def make_school_cake(self): # School.__init__(self) # School.make_cake(self) def make_old_cake(self): super().__init__() super().make_cake() # 4. 测试. if __name__ == '__main__': # 4.1 创建徒弟类对象. p = Prentice() # 4.2 访问属性. print(p.kongfu) # 独创 # 4.3 调用函数. p.make_cake() # 独创 # p.make_master_cake() # 古法 # p.make_school_cake() # AI print('-' * 34) # p.make_cake() # AI p.make_old_cake() ``` ## 多层继承 ```python """ 案例: 演示多层继承. 多层继承解释: 类A继承类B, 类B继承类C, 这就是多层继承. 目前题设中的继承体系 object <- Master, School <- Prentice <- TuSun """ # 故事4: 很多顾客都希望能吃到徒弟做出的有自己独立品牌的煎饼果子,也有黑马配方技术的煎饼果子味道。 # 1. 老师父类. class Master: # 1.1 属性 def __init__(self): self.kongfu = '[古法煎饼果子配方]' # 1.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 2. 黑马学校类 class School: # 2.1 属性 def __init__(self): self.kongfu = '[黑马AI煎饼果子配方]' # 2.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3. 徒弟类 class Prentice(School, Master): # 3.1 属性 def __init__(self): self.kongfu = '[独创煎饼果子配方]' # 3.2 行为 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3.3 调用父类的功能. def make_master_cake(self): Master.__init__(self) Master.make_cake(self) def make_school_cake(self): School.__init__(self) School.make_cake(self) # def make_old_cake(self): # super().__init__() # super().make_cake() # 4.创建徒孙类. class TuSun(Prentice): pass # 5. 测试. if __name__ == '__main__': # 5.1 创建徒孙类对象. ts = TuSun() # 5.2 调用功能. ts.make_cake() # Prentice类的 ts.make_master_cake() # Master类的 ts.make_school_cake() # School类的 ``` ## 封装入门 ```python """ 案例: 演示封装之私有属性. 封装简介: 概述: 属于面向对象的三大特征之一, 就是隐藏对象的属性和实现细节, 仅对外提供公共的访问方式. 怎么封装? 我们学的 函数, 类 都是封装的体现. 好处: 1. 提高代码的安全性. 由 私有化 来保证 2. 提高代码的复用性. 由 函数 来保证 弊端: 代码量增加了. 因为私有内容外界想访问, 必须提供公共的访问方式, 代码量就增加了. 私有格式: __属性名 __函数名() """ # 故事5: 小明把技术给徒孙的时候, 不希望把自己的私房钱给徒孙, 代码模拟. # 1. 定义师傅类Master # 2. 定义学校类School # 3. 定义徒弟类 class Prentice: # 3.1 属性 def __init__(self): self.kongfu = '[黑马煎饼果子配方]' # 私房钱. self.__money = 20000 # 3.2 方法 def make_cake(self): print(f'运用{self.kongfu}制作煎饼果子') # 3.3 针对私有的属性, 提供公共的访问方式. def get_money(self): # 获取 return self.__money def set_money(self, money): # 设置 self.__money = money # 4. 定义徒孙类 class TuSun(Prentice): pass # 5. 测试. if __name__ == '__main__': ts = TuSun() print(ts.kongfu) ts.make_cake() print('-' * 34) # print(ts.__money) # 报错, 父类私有成员, 子类无法访问. ts.set_money(100) print(ts.get_money()) # 通过父类提供的公共的访问方式, 访问父类的私有成员. ``` ## 多态入门 ```python """ 案例: 演示多态入门. 多态概述: 专业版: 同一个函数, 接收不同的参数, 有不同的效果 大白话: 同一个事物在不同时刻表现出来的不同状态, 形态. 前提条件: 1. 要有继承. 2. 要有方法重写, 不然多态无意义. 3. 要有父类引用指向子类对象. 案例: 动物类案例. """ # 1.定义动物类 class Animal: # 抽象类(也叫: 接口) def speak(self): # 抽象方法 pass # 2. 定义子类, 狗类. class Dog(Animal): def speak(self): print('狗叫: 汪汪汪') # 3. 定义子类, 猫类. class Cat(Animal): def speak(self): print('猫叫: 喵喵喵') # 汽车类 class Car: def speak(self): print('车叫: 滴滴滴') # 4. 定义函数, 接收不同的动物对象, 调用speak方法 def make_noise(an:Animal): # an:Animal = Dog() an.speak() # 5. 测试. if __name__ == '__main__': # an:Animal = Dog() # 父类引用指向子类对象. # d:Dog = Dog() # 创建狗类对象. # 5.1 创建狗类, 猫类对象. d = Dog() c = Cat() # 5.2 演示多态. make_noise(d) make_noise(c) print('-' * 34) # 5.3 测试汽车类 c = Car() make_noise(c) ``` ## 多态案例_构建对战平台 ![1741924352691](assets/1741924352691.png) ```python """ 案例: 演示Python的多态案例之 战斗平台. 需求: 1. 构建对战平台(公共的函数) object_play(), 接收: 英雄机 和 敌机. 2. 在不修改对战平台代码的情况下, 完成多次战斗. 3. 规则: 英雄机, 1代战斗力60, 2代战斗力80 敌机, 1代战斗力70 代码提示: 英雄机1代 HeroFighter 英雄机2代 AdvHeroFighter 敌机 EnemyFighter """ # 1. 定义英雄机1代, 战斗力 60 class HeroFighter: def power(self): return 60 # 2. 定义英雄机2代, 战斗力 80 class AdvHeroFighter(HeroFighter): def power(self): return 80 # 3. 敌机1代 class EnemyFighter: def power(self): return 70 # 4. 构建对战平台, 公共的函数, 接收不同的参数, 有不同的效果 -> 多态. # def object_play(hero: HeroFighter, enemy:EnemyFighter): def object_play(hero, enemy): # 参1: 英雄机, 参2: 敌机 if hero.power() >= enemy.power(): print('英雄机 战胜 敌机!') else: print('英雄机 惜败 敌机!') # 5. 测试. if __name__ == '__main__': # 思路1: 不使用多态, 完成对战. # 场景1: 英雄机1代 vs 敌机1代 h1 = HeroFighter() e1 = EnemyFighter() if h1.power() >= e1.power(): print('英雄机1代 战胜 敌机1代') else: print('英雄机1代 惜败 敌机1代') print('-' * 34) # 场景2: 英雄机2代 vs 敌机1代 h2 = AdvHeroFighter() e1 = EnemyFighter() if h2.power() >= e1.power(): print('英雄机2代 战胜 敌机1代') else: print('英雄机2代 惜败 敌机1代') print('*' * 34) # 思路2: 使用多态, 完成对战. h1 = HeroFighter() h2 = AdvHeroFighter() e1 = EnemyFighter() # 场景1: 英雄机1代 vs 敌机1代 object_play(h1, e1) print('-' * 34) # 场景2: 英雄机2代 vs 敌机1代 object_play(h2, e1) # object_play(h2, h1) ``` ## 抽象类案例_空调案例 ```python """ 案例: 演示抽象类的用法. 抽象类解释: 概述: 在Python中, 抽象类 = 接口, 即: 有抽象方法的类就是 抽象类,也叫 接口. 抽象方法 = 没有方法体的方法, 即: 方法体是 pass 修饰的. 作用/目的: 抽象类一般充当父类, 用于指定行业规范, 准则, 具体的实现交由 子类 来完成. """ # 1. 定义抽象类, 空调类, 设定: 空调的规则. class AC: # 1.1 制冷 def cool_wind(self): pass # 1.2 制热 def hot_wind(self): pass # 1.3 左右摆风 def swing_l_r(self): pass # 2. 定义子类(小米空调), 实现父类(空调类)中的所有抽象方法. class XiaoMi(AC): # 2.1 制冷 def cool_wind(self): print('小米 核心 制冷技术!') # 2.2 制热 def hot_wind(self): print('小米 核心 制热技术!') # 2.3 左右摆风 def swing_l_r(self): print('小米空调 静音左右摆风 技术!') # 3. 定义子类(格力空调), 实现父类(空调类)中的所有抽象方法. class Gree(AC): # 3.1 制冷 def cool_wind(self): print('格力 核心 制冷技术!') # 3.2 制热 def hot_wind(self): print('格力 核心 制热技术!') # 3.3 左右摆风 def swing_l_r(self): print('格力空调 低频左右摆风 技术!') # 4. 测试 if __name__ == '__main__': # 4.1 小米空调 xm = XiaoMi() xm.cool_wind() xm.hot_wind() xm.swing_l_r() print('-' * 23) # 4.2 格力空调 gree = Gree() gree.cool_wind() gree.hot_wind() gree.swing_l_r() ``` ## 对象属性和类型属性解释 * 图解 ![1741935509782](assets/1741935509782.png) * 代码演示 ```python """ 案例: 演示对象属性 和 类属性. 属性介绍: 概述: 它是1个名词, 用来描述事物的外在特征的. 分类: 对象属性: 属于每个对象的, 即: 每个对象的属性值可能都不同. 修改A对象的属性, 不影响对象B 类属性: 属于类的, 即: 能被该类下所有的对象所共享. A对象修改类属性, B对象访问的是修改后的. 对象属性: 定义到 init 魔法方法中的属性, 每个对象都有自己的内容. 只能通过 对象名. 的方式调用. 类属性: 定义到类中, 函数外的属性(变量), 能被该类下所有的对象所共享. 既能通过 类名. 还能通过 对象名. 的方式来调用, 推荐使用 类名. 的方式. """ # 需求: 演示 对象属性 和 类属性相关. # 1. 定义1个 Student类, 每个学生都有自己的 姓名, 年龄 class Student: # 2. 定义类属性 teacher_name = '水镜先生' # 3. 定义对象属性, 即: 写到 init 魔法方法中的属性. def __init__(self, name, age): self.name = name self.age = age # 4. 定义str魔法方法, 输出对象的信息. def __str__(self): return '姓名: %s, 年龄: %d' % (self.name, self.age) # 5. 测试 if __name__ == '__main__': # 场景1: 对象属性 s1 = Student('曹操', 38) s2 = Student('曹操', 38) # 修改s1的属性值. s1.name = '许褚' s1.age = 40 print(f's1: {s1}') print(f's2: {s2}') print('-' * 23) # 场景2: 类属性 # 1. 类属性可以通过 类名. 还可以通过 对象名. 的方式调用. print(s1.teacher_name) # 水镜先生 print(s2.teacher_name) # 水镜先生 print(Student.teacher_name) # 水镜先生 print('-' * 23) # 2.尝试用 对象名. 的方式来修改 类属性. # s1.teacher_name = '夯哥' # 只能给s1对象赋值, 不能给类属性赋值. # 3. 如果要修改类变量的值, 只能通过 类名. 的方式实现. Student.teacher_name = '夯哥' print(s1.teacher_name) # 夯哥 print(s2.teacher_name) # 夯哥 print(Student.teacher_name) # 夯哥 ``` ## 类方法和静态方法 ```python """ 案例: 演示类方法和静态方法. 类方法: 属于类的方法, 可以通过 类名. 还可以通过 对象名. 的方式来调用. 定义类方法的时候, 必须使用装饰器 @classmethod, 且第1个参数必须表示 类对象. 静态方法: 属于该类下所有对象所共享的方法, 可以通过 类名. 还可以通过 对象名. 的方式来调用. 定义静态方法的时候, 必须使用装饰器 @staticmethod, 且参数传不传都可以. 区别: 1. 类方法的第1个参数必须是 类对象, 静态方法无参数的特殊要求 2. 你可以理解为: 如果函数中要用 类对象, 就定义成类方法, 否则定义成 静态方法, 除此外, 并无任何区别. """ # 1. 定义学生类. class Student: # 2. 定义类属性. school = '黑马程序员' # 3. 定义类方法 @classmethod def show1(cls): print(f'cls: {cls}') # print(cls.school) print('我是类方法') # 4. 定义静态方法 @staticmethod def show2(): print(Student.school) print('我是静态方法') # 5. 测试. if __name__ == '__main__': s1 = Student() s1.show1() print('-' * 23) s1.show2() ``` ## 学生管理系统_学生类代码编写 > 如下是写到 **student.py** 文件中的代码 ```python """ 该文件用于记录 学生类, 学生的属性信息为: 姓名, 性别, 年龄, 手机号, 描述信息. """ # 1. 定义学生类. class Student: # 2. 定义魔法方法, 初始化属性信息. def __init__(self, name, gender, age, phone, desc): """ 该魔法方法, 用于初始化 属性信息. :param name: 学生姓名 :param gender: 性别 :param age: 年龄 :param phone: 手机号 :param desc: """ self.name = name self.gender = gender self.age = age self.phone = phone self.desc = desc # 3. 定义魔法方法, 用于打印学生信息. def __str__(self): """ 该魔法方法, 用于打印学生信息. :return: """ return f'姓名: {self.name}, 性别: {self.gender}, 年龄: {self.age}, 手机号: {self.phone}, 描述信息: {self.desc}' # 4. 测试 if __name__ == '__main__': s = Student('乔峰', '男', 38, '13112345678', '丐帮帮主') print(s) ``` ## 学生管理系统_框架搭建 > 如下是写到 **studentcms.py** 文件中的内容. ```python """ 该文件用于 完成学生管理系统的 具体业务的操作, 即: 增删改查, 保存学生信息等... """ # 导包 from student import Student # 1. 创建学生管理系统类. class StudentCMS(object): # 2. 通过魔法方法init, 初始化属性信息. def __init__(self): # 创建一个空列表, 用于存储学生信息. self.stu_list = [] # [学生对象, 学生对象, 学生对象] -> [Student(...), Student(...)...] # 3. 定义函数, 实现打印 管理系统的界面. def show_view(self): print('*' * 23) print('学生管理系统V2.0版') print('\t1.添加学生信息') print('\t2.删除学生信息') print('\t3.修改学生信息') print('\t4.查询单个学生信息') print('\t5.查询所有学生信息') print('\t6.保存学生信息') print('\t0.退出系统') print('*' * 23) # 4. 定义函数, 实现添加学生信息功能. def add_student(self): pass # 5. 定义函数, 实现删除学生信息功能. def del_student(self): pass # 6. 定义函数, 实现修改学生信息功能. def update_student(self): pass # 7. 定义函数, 实现查询单个学生信息功能. def search_one_student(self): pass # 8. 定义函数, 实现查询所有学生信息功能. def search_all_student(self): pass # 9. 定义函数, 实现保存学生信息功能. def save_student(self): pass # 10. 定义函数, 实现加载学生信息. def load_student(self): pass # 11. 定义函数, 把上述的所有业务逻辑跑通. def start(self): # 11.1 # 11.2 死循环, 不断的玩儿. while True: # 11.3 # 11.4 打印 学生管理系统的界面. self.show_view() # 11.5 提示用户录入要操作的编号, 并接收. input_num = input('请输入您要操作的编号:') # 11.6 根据用户输入的编号, 做不同的操作. if input_num == '1': # 添加学生信息 print('添加学生信息\n') self.add_student() elif input_num == '2': # 删除学生信息 print('删除学生信息\n') self.del_student() elif input_num == '3': # 修改学生信息 print('修改学生信息\n') self.update_student() elif input_num == '4': # 查询单个学生信息 print('查询单个学生信息\n') self.search_one_student() elif input_num == '5': # 查询所有学生信息 print('查询所有学生信息\n') self.search_all_student() elif input_num == '6': # 保存学生信息 print('保存学生信息\n') self.save_student() elif input_num == '0': # 退出系统, 做二次校验. result = input('您确定要退出吗? (Y/N) -> ') if result.lower() == 'y': # 字符串的lower() -> 把字母转成小写形式. print('谢谢您的使用, 期待下次再会!') break else: # 输入错误 print('录入有误, 请重新录入!\n') # 12. 在main中测试. if __name__ == '__main__': # 12.1 创建学生管理系统对象. cms = StudentCMS() # 12.2 调用学生管理系统对象的start()函数, 启动学生管理系统. cms.start() ``` ## 学生管理系统_入口文件 > 如下的代码是写到 **main.py** 文件中的. ```python """ 该文件 用作程序的入口文件. """ from studentcms import StudentCMS # 程序的主入口 if __name__ == '__main__': # 1. 创建学生管理系统对象. stu_cms = StudentCMS() # 2. 启动程序即可. stu_cms.start() ``` ## 学生管理系统_功能实现 * 添加学生 ```python # 4. 定义函数, 实现添加学生信息功能. def add_student(self): # 4.1 提示用户输入学生信息, 并接收. name = input('请输入学生姓名:') gender = input('请输入学生性别:') age = int(input('请输入学生年龄:')) phone = input('请输入学生电话:') desc = input('请输入学生描述信息:') # 4.2 把上述的信息封装成学生对象. stu = Student(name, gender, age, phone, desc) # 4.3 把学生对象添加到列表中. self.stu_list.append(stu) # 4.4 提示. print(f'添加 {name} 学生信息成功!\n') ``` * 查看所有学生信息 ```python # 8. 定义函数, 实现查询所有学生信息功能. def search_all_student(self): # 8.1 判断列表长度是否为0, 如果为0, 提示: 暂无学生信息, 请添加后查询. if len(self.stu_list) == 0: print('暂无学生信息, 请添加后查询! \n') else: # 8.2 如果长度不为0, 遍历列表, 打印出所有的学生信息. for stu in self.stu_list: print(stu) print() # 为了格式好看, 加个换行. ``` * 删除学生信息 ```python # 5. 定义函数, 实现删除学生信息功能. def del_student(self): # 5.1 提示用户输入要删除的学生的姓名, 并接收. del_name = input('请输入要删除的学生姓名:') # 5.2 遍历列表, 找到要删除的学生, 并删除. for stu in self.stu_list: # 5.3 如果当前学生的姓名 和 要删除的学生相同, 就删除该学生信息 if stu.name == del_name: self.stu_list.remove(stu) print(f'学员 {del_name} 信息删除成功!\n') break else: # 走到这里, 说明没有走break, 即: 没有找到这个学生. print('查无此人, 请检查后重新删除!\n') ``` * 修改学生信息 ```python # 6. 定义函数, 实现修改学生信息功能. def update_student(self): # 6.1 提示用户输入要修改的学生的姓名, 并接收. upd_name = input('请输入要修改的学生姓名:') # 6.2 遍历列表, 找到要修改的学生, 并修改. for stu in self.stu_list: # 6.3 如果当前学生的姓名 和 要修改的学生相同, 就修改该学生信息 if stu.name == upd_name: # 6.4 提示用户录入该学员新的信息. stu.gender = input('请录入修改后的性别: ') stu.age = int(input('请录入修改后的年龄: ')) stu.phone = input('请录入修改后的电话: ') stu.desc = input('请录入修改后的描述信息: ') print(f'学员 {upd_name} 信息修改成功!\n') break else: # 走到这里, 说明没有走break, 即: 没有找到这个学生. print('查无此人, 请检查后重新操作!\n') ``` * 查询单个学生信息 ```python # 7. 定义函数, 实现查询单个学生信息功能. def search_one_student(self): # 7.1 提示用户输入要查找的学生的姓名, 并接收. search_name = input('请输入要查找的学生姓名:') # 7.2 遍历列表, 找到要查找的学生, 并打印信息. for stu in self.stu_list: # 7.3 如果当前学生的姓名 和 要查找的学生相同, 就打印该学生信息 if stu.name == search_name: print(stu, end='\n\n') break else: # 走到这里, 说明没有走break, 即: 没有找到这个学生. print('查无此人, 请检查后重新操作!\n') ``` ## 扩展_dict属性 ```python """ 案例: 演示Python内置的dict属性. __dict__ 属性介绍: 它是Python内置的属性, 可以把对象转成字典形式. """ from 学生管理系统_面向对象版.student import Student # 需求1: 把 学生对象 -> 字典形式, 属性名做键, 属性值做值. s1 = Student('德桦', '男', 81, '111', '刻骨铭心') print(s1) # {'name': '德桦', 'gender': '男', 'age': 81, 'phone': '111', 'desc': '刻骨铭心'} my_dict = s1.__dict__ print(my_dict) print(type(my_dict)) print('-' * 23) # 需求2: 把 [学生对象, 学生对象, 学生对象] -> [字典, 字典, 字典] s1 = Student('德桦', '男', 81, '111', '刻骨铭心') s2 = Student('志奇', '男', 22, '222', '我不是紫琦') s3 = Student('紫琦', '男', 66, '333', '有请志奇') stu_list = [s1, s2, s3] # 列表推导式. list_dict = [stu.__dict__ for stu in stu_list] print(list_dict) print('-' * 23) # 需求3: 把 {'name': '德桦', 'gender': '男', 'age': 81, 'phone': '111', 'desc': '刻骨铭心'} -> 学生对象 my_dict = {'name': '德桦', 'gender': '男', 'age': 81, 'phone': '111', 'desc': '刻骨铭心'} s5 = Student(my_dict['name'], my_dict['gender'], my_dict['age'], my_dict['phone'], my_dict['desc']) print(s5) print(type(s5)) print('-' * 23) s6 = Student(**my_dict) # 效果同上 print(s6) print(type(s6)) ``` ## 学生管理学系统_保存学生信息 ```python # 9. 定义函数, 实现保存学生信息功能. def save_student(self): # 9.1 关联 学生信息文件. with open('./stu_data.txt', 'w', encoding='utf-8') as dest_f: # 9.2 把 [学生对象, 学生对象...] -> [字典, 字典...] stu_dict = [stu.__dict__ for stu in self.stu_list] # 9.3 把字典列表, 持久化到文件中. dest_f.write(str(stu_dict)) # 记得转成字符串再写入. ``` ## 学生管理系统_加载学生信息 ```python # 10. 定义函数, 实现加载学生信息. def load_student(self): # 10.1 加入异常处理, 有可能文件不存在. try: # 10.2 关联学生信息文件. with open('./stu_data.txt', 'r', encoding='utf-8') as src_f: # 10.3 一次性读取所有数据. stu_data = src_f.read() # '[字典, 字典...]' # 10.4 把上述的字符串, 转为列表. stu_list = eval(stu_data) # '' # 10.5 判断如果列表为空, 就赋予空列表. if len(stu_list) == 0: stu_list = [] # 10.6 把stu_list(列表套字典) 转成 [学生对象, 学生对象...], 并赋值给 self.stu_list self.stu_list = [Student(**stu_dict) for stu_dict in stu_list] except: # 10.7 走这里, 说明目的地文件不存在, 创建即可. with open('./stu_data.txt', 'w', encoding='utf-8') as src_f: pass ``` ## 学生管理系统_最终代码 * **student.py** 文件中的代码 ```python """ 该文件用于记录 学生类, 学生的属性信息为: 姓名, 性别, 年龄, 手机号, 描述信息. """ # 1. 定义学生类. class Student: # 2. 定义魔法方法, 初始化属性信息. def __init__(self, name, gender, age, phone, desc): """ 该魔法方法, 用于初始化 属性信息. :param name: 学生姓名 :param gender: 性别 :param age: 年龄 :param phone: 手机号 :param desc: """ self.name = name self.gender = gender self.age = age self.phone = phone self.desc = desc # 3. 定义魔法方法, 用于打印学生信息. def __str__(self): """ 该魔法方法, 用于打印学生信息. :return: """ return f'姓名: {self.name}, 性别: {self.gender}, 年龄: {self.age}, 手机号: {self.phone}, 描述信息: {self.desc}' # 4. 测试 if __name__ == '__main__': s = Student('乔峰', '男', 38, '13112345678', '丐帮帮主') print(s) ``` * **studentcms.py** 文件中的代码 ```python """ 该文件用于 完成学生管理系统的 具体业务的操作, 即: 增删改查, 保存学生信息等... """ # 导包 from student import Student import time # 1. 创建学生管理系统类. class StudentCMS(object): # 2. 通过魔法方法init, 初始化属性信息. def __init__(self): # 创建一个空列表, 用于存储学生信息. self.stu_list = [] # [学生对象, 学生对象, 学生对象] -> [Student(...), Student(...)...] # self.stu_list = [ # Student('德桦', '男', 81, '111', '刻骨铭心'), # Student('志奇', '男', 22, '222', '我不是紫琦'), # Student('紫琦', '男', 66, '333', '有请志奇'), # Student('冷哥', '男', 88, '444', '谁动了我的水冷'), # Student('卷帘', '男', 52, '555', '谁动了我的大酱'), # ] # 3. 定义函数, 实现打印 管理系统的界面. # 因为该函数中没有使用self, 所以可以把该函数定义为静态方法. @staticmethod def show_view(): print('*' * 23) print('学生管理系统V2.0版') print('\t1.添加学生信息') print('\t2.删除学生信息') print('\t3.修改学生信息') print('\t4.查询单个学生信息') print('\t5.查询所有学生信息') print('\t6.保存学生信息') print('\t0.退出系统') print('*' * 23) # 4. 定义函数, 实现添加学生信息功能. def add_student(self): # 4.1 提示用户输入学生信息, 并接收. name = input('请输入学生姓名:') gender = input('请输入学生性别:') age = int(input('请输入学生年龄:')) phone = input('请输入学生电话:') desc = input('请输入学生描述信息:') # 4.2 把上述的信息封装成学生对象. stu = Student(name, gender, age, phone, desc) # 4.3 把学生对象添加到列表中. self.stu_list.append(stu) # 4.4 提示. print(f'添加 {name} 学生信息成功!\n') # 5. 定义函数, 实现删除学生信息功能. def del_student(self): # 5.1 提示用户输入要删除的学生的姓名, 并接收. del_name = input('请输入要删除的学生姓名:') # 5.2 遍历列表, 找到要删除的学生, 并删除. for stu in self.stu_list: # 5.3 如果当前学生的姓名 和 要删除的学生相同, 就删除该学生信息 if stu.name == del_name: self.stu_list.remove(stu) print(f'学员 {del_name} 信息删除成功!\n') break else: # 走到这里, 说明没有走break, 即: 没有找到这个学生. print('查无此人, 请检查后重新删除!\n') # 6. 定义函数, 实现修改学生信息功能. def update_student(self): # 6.1 提示用户输入要修改的学生的姓名, 并接收. upd_name = input('请输入要修改的学生姓名:') # 6.2 遍历列表, 找到要修改的学生, 并修改. for stu in self.stu_list: # 6.3 如果当前学生的姓名 和 要修改的学生相同, 就修改该学生信息 if stu.name == upd_name: # 6.4 提示用户录入该学员新的信息. stu.gender = input('请录入修改后的性别: ') stu.age = int(input('请录入修改后的年龄: ')) stu.phone = input('请录入修改后的电话: ') stu.desc = input('请录入修改后的描述信息: ') print(f'学员 {upd_name} 信息修改成功!\n') break else: # 走到这里, 说明没有走break, 即: 没有找到这个学生. print('查无此人, 请检查后重新操作!\n') # 7. 定义函数, 实现查询单个学生信息功能. def search_one_student(self): # 7.1 提示用户输入要查找的学生的姓名, 并接收. search_name = input('请输入要查找的学生姓名:') # 7.2 遍历列表, 找到要查找的学生, 并打印信息. for stu in self.stu_list: # 7.3 如果当前学生的姓名 和 要查找的学生相同, 就打印该学生信息 if stu.name == search_name: print(stu, end='\n\n') break else: # 走到这里, 说明没有走break, 即: 没有找到这个学生. print('查无此人, 请检查后重新操作!\n') # 8. 定义函数, 实现查询所有学生信息功能. def search_all_student(self): # 8.1 判断列表长度是否为0, 如果为0, 提示: 暂无学生信息, 请添加后查询. if len(self.stu_list) == 0: print('暂无学生信息, 请添加后查询! \n') else: # 8.2 如果长度不为0, 遍历列表, 打印出所有的学生信息. for stu in self.stu_list: print(stu) print() # 为了格式好看, 加个换行. # 9. 定义函数, 实现保存学生信息功能. def save_student(self): # 9.1 关联 学生信息文件. with open('./stu_data.txt', 'w', encoding='utf-8') as dest_f: # 9.2 把 [学生对象, 学生对象...] -> [字典, 字典...] stu_dict = [stu.__dict__ for stu in self.stu_list] # 9.3 把字典列表, 持久化到文件中. dest_f.write(str(stu_dict)) # 记得转成字符串再写入. # 10. 定义函数, 实现加载学生信息. def load_student(self): # 10.1 加入异常处理, 有可能文件不存在. try: # 10.2 关联学生信息文件. with open('./stu_data.txt', 'r', encoding='utf-8') as src_f: # 10.3 一次性读取所有数据. stu_data = src_f.read() # '[字典, 字典...]' # 10.4 把上述的字符串, 转为列表. stu_list = eval(stu_data) # '' # 10.5 判断如果列表为空, 就赋予空列表. if len(stu_list) == 0: stu_list = [] # 10.6 把stu_list(列表套字典) 转成 [学生对象, 学生对象...], 并赋值给 self.stu_list self.stu_list = [Student(**stu_dict) for stu_dict in stu_list] except: # 10.7 走这里, 说明目的地文件不存在, 创建即可. with open('./stu_data.txt', 'w', encoding='utf-8') as src_f: pass # 11. 定义函数, 把上述的所有业务逻辑跑通. def start(self): # 11.1 加载学生信息. self.load_student() # 11.2 死循环, 不断的玩儿. while True: # 11.3 为了效果更明显, 加入: 延迟(休眠线程) time.sleep(1) # 11.4 打印 学生管理系统的界面. StudentCMS.show_view() # 11.5 提示用户录入要操作的编号, 并接收. input_num = input('请输入您要操作的编号:') # 11.6 根据用户输入的编号, 做不同的操作. if input_num == '1': # 添加学生信息 # print('添加学生信息\n') self.add_student() elif input_num == '2': # 删除学生信息 # print('删除学生信息\n') self.del_student() elif input_num == '3': # 修改学生信息 # print('修改学生信息\n') self.update_student() elif input_num == '4': # 查询单个学生信息 # print('查询单个学生信息\n') self.search_one_student() elif input_num == '5': # 查询所有学生信息 # print('查询所有学生信息\n') self.search_all_student() elif input_num == '6': # 保存学生信息 self.save_student() print('保存学生信息成功!\n') elif input_num == '0': # 退出系统, 做二次校验. result = input('您确定要退出吗? (Y/N) -> ') if result.lower() == 'y': # 字符串的lower() -> 把字母转成小写形式. # 在退出前, 自动保存学生数据到文件. self.save_student() print('谢谢您的使用, 期待下次再会!') break else: # 输入错误 print('录入有误, 请重新录入!\n') # 12. 在main中测试. if __name__ == '__main__': # 12.1 创建学生管理系统对象. cms = StudentCMS() # 12.2 调用学生管理系统对象的start()函数, 启动学生管理系统. cms.start() # import os # print(os.getcwd()) ``` * **main.py** 文件中的代码 ```python """ 该文件 用作程序的入口文件. """ from studentcms import StudentCMS # 程序的主入口 if __name__ == '__main__': # 1. 创建学生管理系统对象. stu_cms = StudentCMS() # 2. 启动程序即可. stu_cms.start() ``` ## 闭包背景介绍 ```python """ 案例: 闭包背景介绍 案例目的: 引出来 闭包 相关的知识点. """ # 需求: 定义函数保存变量10, 调用函数返回值 并 重复累加数值, 观察结果. # 1. 定义函数, 保存变量10 def func(): num = 10 return num # 2. 调用函数, 获取返回值. num = func() print(num + 1) # 11 print(num + 1) # 11 print(num + 1) # 11 ``` ## 闭包入门 ![1742090722484](assets/1742090722484.png) ```python """ 案例: 闭包入门. 闭包解释: 概述: 内部函数 使用了外部函数的变量, 这种写法就称之为闭包. 格式: def 外部函数名(形参列表): 外部函数的(局部)变量 def 内部函数名(形参列表): 使用外部函数的变量 return 内部函数名 前提条件: 1. 有嵌套. 外部函数嵌套内部函数 2. 有引用. 内部函数使用外部函数的变量 3. 有返回. 外部函数中, 返回 内部函数名(对象) 细节: 1. 函数名 和 函数名() 是两个概念, 前者表示 函数对象, 后者表示 调用函数, 获取返回值. """ # 案例1: 函数名 -> 是对象 def get_sum(a, b): return a + b print(get_sum) # , 对象. print(get_sum(10, 20)) # 调用函数, 获取返回值. # 函数名可以赋值给变量, 这个变量就是: 函数对象. my_sum = get_sum print(my_sum) # print(my_sum(100, 200)) # 300 print('-' * 23) # 案例2: 演示闭包写法. # 需求: 定义求和的闭包, 外部函数有参数num1, 内部函数有参数num2, 调用, 求解两数之和, 观察结果. # 1. 定义外部函数. def fn_outer(num1): # 2. 定义内部函数 def fn_inner(num2): # 有嵌套 # 3. 求和 sum = num1 + num2 # 有引用 print(f'求和结果: {sum}') return fn_inner # 有返回 # 4.调用上述的函数 fn_inner = fn_outer(10) fn_inner(20) print('-' * 23) fn_outer(100)(200) ``` ## nonlocal关键字介绍 * 图解 ![1742091801009](assets/1742091801009.png) * 代码 ```python """ 案例: nonlocal关键字介绍 nonlocal: 它是Python内置的关键字, 可以实现 在内部函数中 修改外部函数的 变量值. """ # 需求: 编写1个闭包,让内部函数访问外部函数的参数 a = 100, 并观察结果. # 1. 定义外部函数. def fn_outer(): # 2.定义外部函数的(局部)变量 a = 100 # 3.定义内部函数, 访问外部函数的变量. def fn_inner(): # 4.在内部函数中修改外部函数的变量 nonlocal a # nonlocal: 可以实现在内部函数中修改外部函数的变量值. a = a + 1 # 5. 打印外部函数的变量 print(f'a: {a}') # 6. 返回 内部函数名(对象) return fn_inner # 7.测试 if __name__ == '__main__': fn_inner = fn_outer() fn_inner() # 101 fn_inner() # 102 fn_inner() # 103 ``` ## 装饰器入门 * 图解 ![1742094968294](assets/1742094968294.png) * 代码 ```python """ 案例: 装饰器入门. 装饰器介绍: 概述/作用: 它的本质是1个闭包函数, 目的是 在不改变原有函数的基础上, 对其功能做增强. 大白话: 装修队 在不改变房屋结构的情况下, 对房屋做装饰(功能增强) 前提条件: 1. 有嵌套. 2. 有引用. 3. 有返回. 4. 有额外功能. 装饰器的用法: 格式1: 传统写法. 装饰后的函数名 = 装饰器名(被装饰的原函数名) 装饰后的函数名() 格式2: 语法糖. 在要被装饰的原函数上, 直接写 @装饰器名, 之后直接调用原函数即可. """ # 需求: 在发表评论前, 都是需要先登录的. # 1.定义外部函数, 形参列表接收 要被装饰的函数名(对象) def check_login(fn_name): # fn_name: 被装饰的函数名(对象) # 1.1 定义内部函数. def fn_inner(): # 有嵌套 # 1.2 额外功能 print('校验登陆... 登陆成功!') # 1.3 访问原函数, 即: 外部函数的引用. fn_name() # 有引用 # 1.4 返回内部函数对象. return fn_inner # 有返回 # 2.定义函数, 表示 发表评论. def comment(): print("发表评论") @check_login # 底层其实是: payment = check_login(payment) def payment(): print('充值中...') # 3. 测试. # 3.1 传统方式. comment = check_login(comment) comment() print('-' * 23) # 3.2 语法糖 # payment = check_login(payment) # payment() payment() ``` ## 装饰器案例 * 场景1: ==**无参无返回值的原函数**== ![1742097567109](assets/1742097567109.png) ```python """ 案例: 装饰器装饰_无参无返回的原函数 细节: 装饰器的内部函数格式 要和 被装饰的原函数 保持一致, 即: 原函数是无参无返回的, 则 装饰器的内部函数也必须是 无参无返回的. 原函数有参有返回的, 则 装饰器的内部函数也必须是 有参有返回的. """ # 需求: 定义无参无返回值的 get_sum()求和函数, 在不改变其代码的基础上, 添加友好提示: 正在努力计算中... # 1. 定义装饰器. def my_decorator(fn_name): # 1.1 定义内部函数, 其格式必须和 被装饰的原函数 保持一致. def fn_inner(): # 有嵌套 # 1.2 添加提示信息(额外功能) print('正在努力计算中...') # 有额外功能 # 1.3 调用原函数. fn_name() # 有引用 # 1.4 返回内部函数(对象) return fn_inner # 有返回 # 2. 定义原函数. @my_decorator def get_sum(): a = 10 b = 20 sum = a + b print(f'sum求和结果: {sum}') # 3.测试. # 3.1 传统方式. # get_sum = my_decorator(get_sum) # get_sum() # 3.2 语法糖. get_sum() ``` * 场景2: ==**有参无返回值的原函数**== ```python """ 案例: 装饰器装饰_有参无返回的原函数 细节: 装饰器的内部函数格式 要和 被装饰的原函数 保持一致, 即: 原函数是无参无返回的, 则 装饰器的内部函数也必须是 无参无返回的. 原函数有参有返回的, 则 装饰器的内部函数也必须是 有参有返回的. """ # 需求: 定义有参无返回值的 get_sum()求和函数, 在不改变其代码的基础上, 添加友好提示: 正在努力计算中... # 1. 定义装饰器. def my_decorator(fn_name): # 1.1 定义内部函数 def fn_inner(x, y): # 1.2 额外功能 print('正在努力计算中...') # 1.3 调用原函数. fn_name(x, y) # 1.4 返回内部函数. return fn_inner # 2. 定义原函数, 有参无返回值. @my_decorator def get_sum(a, b): sum = a + b print(f'sum求和结果: {sum}') # 3.测试. # 3.1 传统方式. # get_sum = my_decorator(get_sum) # get_sum(10, 20) # 3.2 语法糖. get_sum(10, 20) ``` ## 网络编程介绍 * 概述 > 就是用来实现网络互联的 不同计算机上 运行的程序间 可以进行数据交互. * 三要素 * IP地址: 设备(电脑, 手机, Ipad...)在网络中的唯一标识 > 分类: > > ​ IPV4, 4字节, 十进制. 例如: 192.168.88.100 > > ​ IPV6, 8字节, 十六进制, 宣传语: 可以让地球上的每一粒沙子都有自己的IP > > 两个DOS命令: > > ​ 查看IP: > > ​ windows: ipconfig > > ​ Linux, Mac: ifconfig > > ​ 测试网络连接: > > ​ ping ip地址 或者 域名 * 端口号: 程序在设备(电脑, 手机, Ipad...)上的唯一标识. > 范围: 0 ~ 65535, 其中0 ~ 1023已经被系统占用或者用作保留端口, 自定义端口时尽量规避这个范围. * 协议: 传输规则, 规范. > 常用的协议: > > ​ TCP(这个用的最多) 和 UDP > > TCP特点: > > ​ 1.面向有连接 > > ​ 2.采用字节流传输数据, 理论无大小限制. > > ​ 3.安全(可靠)协议. > > ​ 4.效率相对较低. > > ​ 5.区分客户端和服务器端. ## 入门创建Socket对象 ```python """ 案例: 演示socket对象的创建. 网络编程介绍: 概述: 网络编程也叫网络通信, Socket通信, 即: 通信双方都独有自己的Socket对象, 数据在Socket之间通过 数据报包(UDP协议) 或者 字节流(TCP协议) 的形式进行传输. 大白话举例: 你和你遥远的朋友在聊天, 看看这你们两个人在交互, 其实是通过 两部手机(双方各自的手机)来交互的. """ # 导包 import socket # 创建Socket对象 # 参1: Address Family, 地址族, 即: Ipv4 还是 IpV6, 默认值: AF_INET(ipv4) AF_INET6(ipv6) # 参2: Socket Type, Socket类型, 即: TCP 还是 UDP, 默认值: SOCK_STREAM(TCP) SOCK_DGRAM(UDP) socket_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print(socket_obj) ``` ## 网编案例_一句话交情 * 图解 ![1742178741721](assets/1742178741721.png) * 服务器端代码 ```python """ 案例: 网编入门案例, 服务器端给客户端发送消息, 客户端给出回执信息. 服务器端开发流程: 1. 创建服务器端Socket对象. 2. 绑定IP地址和端口号. 3. 设置最大监听数. 4. 等待客户端申请建立连接. 5. 给客户端发送消息. 6. 接收客户端的信息并打印. 7. 释放资源. 细节: 客户端和服务器端是通过 字节流(bytes) 的形式实现的. """ # 导包 import socket # 1. 创建服务器端Socket对象. ipv4, 字节流(TCP) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 绑定IP地址和端口号. server_socket.bind(('192.168.22.51', 10086)) # 3. 设置最大监听数. server_socket.listen(5) # 4. 等待客户端申请建立连接. accept_socket, client_info = server_socket.accept() # 5. 给客户端发送消息. accept_socket.send(b'Welcome To Socket!') # 6. 接收客户端的信息并打印. data = accept_socket.recv(1024).decode('utf-8') print(f'服务器端收到 来自{client_info} 的信息: {data}') # 7. 释放资源. accept_socket.close() # server_socket.close() # 服务器端一般不关闭. ``` * 客户端代码 ```python """ 案例: 网编入门案例, 服务器端给客户端发送消息, 客户端给出回执信息. 客户端开发流程: 1. 创建客户端Socket对象. 2. 连接服务器端, 指定: 服务器端IP, 端口号. 3. 接收服务器端的信息并打印. 4. 给服务器端发送消息. 5. 释放资源. 细节: 客户端和服务器端是通过 字节流(bytes) 的形式实现的. """ # 导包 import socket # 1. 创建客户端Socket对象. ipv4, TCP协议 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务器端, 指定: 服务器端IP, 端口号. client_socket.connect(('192.168.22.51', 10086)) # 3. 接收服务器端的信息并打印. data = client_socket.recv(1024).decode('utf-8') print(f'客户端收到: {data}') # 4. 给服务器端发送消息. client_socket.send('Socket很好玩儿, 很有趣, 我很喜欢!'.encode('utf-8')) # 5. 释放资源. client_socket.close() ``` ## 网编案例_模拟多任务服务器端 ```python """ 案例: 网编入门案例, 服务器端给客户端发送消息, 客户端给出回执信息. 服务器端开发流程: 1. 创建服务器端Socket对象. 2. 绑定IP地址和端口号. 3. 设置最大监听数. 4. 等待客户端申请建立连接. 5. 给客户端发送消息. 6. 接收客户端的信息并打印. 7. 释放资源. 细节: 客户端和服务器端是通过 字节流(bytes) 的形式实现的. """ # 导包 import socket # 1. 创建服务器端Socket对象. ipv4, 字节流(TCP) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 绑定IP地址和端口号. server_socket.bind(('192.168.22.51', 10086)) # 3. 设置最大监听数. server_socket.listen(5) while True: try: # 4. 等待客户端申请建立连接. accept_socket, client_info = server_socket.accept() # 5. 给客户端发送消息. accept_socket.send(b'Welcome To Socket!') # 6. 接收客户端的信息并打印. data = accept_socket.recv(1024).decode('utf-8') print(f'服务器端收到 来自{client_info} 的信息: {data}') # print(f'服务器端收到: {data}') # 7. 释放资源. accept_socket.close() # server_socket.close() # 服务器端一般不关闭. except: pass # 扩展: 设置端口号重用, 目的是: 快速重启服务器(服务器关闭后, 立即释放端口). # 参1: 当前的套接字对象, 参2: 选项名, 参3: 该选项的值 # server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) ``` ## 扩展_编解码 ```python """ 案例: 演示编解码. 细节: 1. 编码 = 把我们看懂的 转成 我们看不懂的. '字符串'.encode(码表) 2. 解码 = 把我们看不懂的 转成 我们看懂的. 二进制.decode(码表) 3. 只要乱码了, 原因只有1个, 编解码不同. 4. 英文字母, 数字, 特殊符号无论什么码表都只占1个字节, 中文在gbk占2个字节, utf-8中占3个字节. 5. 二进制数据特殊写法, 即: b'字母 数字 特俗符号', 该方式针对于中文无效. """ # 需求1: 编码. # s1 = '黑马' s1 = '黑马123abCD!@#' print(s1.encode()) # b'\xe9\xbb\x91\xe9\xa9\xac123abCD!@#' print(s1.encode('utf-8')) # b'\xe9\xbb\x91\xe9\xa9\xac123abCD!@#' print(s1.encode('gbk')) # b'\xba\xda\xc2\xed123abCD!@#' print('-' * 23) # 需求2: 解码 bys = b'\xe9\xbb\x91\xe9\xa9\xac123abCD!@#' print(type(bys)) # s2 = bys.decode() s3 = bys.decode('utf-8') print(s2) print(s3) print('-' * 23) s4 = bys.decode('gbk') print(s4) # 榛戦┈123abCD!@# ``` ## 网编案例_文件上传 * 图解 ![1742186023575](assets/1742186023575.png) * 服务器端代码 ```python """ 案例: 文件上传案例, 服务器端代码. 回顾: 网编服务器端实现流程. 1. 创建服务器端Socket对象. 2. 绑定ip 和 端口号. 3. 设置最大监听数. 4. 等待客户端申请建立连接 5. 读取客户端上传的(文件)数据, 写到目的地文件 6. 释放资源. """ # 导包 import socket # 1. 创建服务器端Socket对象. server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 绑定ip 和 端口号. server_socket.bind(("192.168.22.51", 6666)) # 3. 设置最大监听数. server_socket.listen(5) # 4. 等待客户端申请建立连接 accept_socket, client_info = server_socket.accept() # 5. 读取客户端上传的(文件)数据 # 5.1 关联目的地文件. with open('./data/my.txt', 'wb') as dest_f: # 5.2 循环读取数据 while True: # 5.3 接收客户端上传的文件数据. bys = accept_socket.recv(8192) # 8192字节 = 8kb # 5.4 判断是否读取到数据, 无数据(说明客户端断开连接)结束即可 if len(bys) == 0: break # 5.5 把读取到的数据写入到目的地文件中. dest_f.write(bys) # 7. 释放资源. accept_socket.close() ``` * 客户端 ```python """ 案例: 网编入门案例, 服务器端给客户端发送消息, 客户端给出回执信息. 客户端开发流程: 1. 创建客户端Socket对象. 2. 连接服务器端, 指定: 服务器端IP, 端口号. 3. 接收服务器端的信息并打印. 4. 给服务器端发送消息. 5. 释放资源. 细节: 客户端和服务器端是通过 字节流(bytes) 的形式实现的. """ # 导包 import socket # 1. 创建客户端Socket对象. ipv4, TCP协议 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务器端, 指定: 服务器端IP, 端口号. client_socket.connect(('192.168.22.51', 10086)) # 3. 接收服务器端的信息并打印. data = client_socket.recv(1024).decode('utf-8') print(f'客户端收到: {data}') # 4. 给服务器端发送消息. client_socket.send('Socket很好玩儿, 很有趣, 我很喜欢!'.encode('utf-8')) # 5. 释放资源. client_socket.close() ``` ## 扩展_模拟多任务版文件上传服务器端 ```python ``` ## 多任务简介 * 概述 > 让多个任务"同时"执行, 目的是: 充分利用CPU资源, 提高程序的执行效率. * 方式 * 并发: 针对于单核CPU来讲, 多个任务同时请求执行时, CPU做高效切换即可. * 并行:针对于多核CPU来讲, 多个任务同时执行. * 图解 ![1742195490886](assets/1742195490886.png) ## 单任务代码演示 ```python """ 案例: 演示单任务, 前边不执行完毕, 后边绝对无法执行. """ # 1.定义函数A, 输出10次 hello world def func_a(): for i in range(1000000): print("hello world") # 2. 定义函数B, 输出10次 hello python def func_b(): for i in range(2): print("hello python") func_a() print('-' * 23) func_b() ``` ## 多进程入门案例 * 入门案例 ```python """ 案例: 演示多进程入门案例. 多进程目的: 它属于多任务的一种实现方式, 目的是充分利用CPU资源, 提高程序执行效率. 实现方式: 1. 导包. 2. 创建进程对象, 关联目标函数. 3. 启动进程. """ # 导包 import multiprocessing import time # 1. 定义函数 表示 编写代码. def coding(): for i in range(1, 11): time.sleep(0.1) # 可以模拟耗时操作, 更好的查看多任务的执行效果. print(f'正在敲第 {i} 遍代码!') # 2. 定义函数 表示 听音乐。 def music(): for i in range(1, 11): time.sleep(0.1) print(f'正在听第 {i} 遍音乐......') # 3. 创建两个进程对象, 分别关联上述的两个 目标函数. # 细节: 通过main进程(主进程)来创建子进程. if __name__ == '__main__': # 单任务 # coding() # music() # 进程p1关联 coding函数, p1进程抢到(CPU资源了), 就会执行这个函数. p1 = multiprocessing.Process(target=coding) p2 = multiprocessing.Process(target=music) # 4. 启动进程. 大白话: 表示进程启动了, 就可以开始抢CPU资源了. p1.start() p2.start() ``` * 带参数的多进程代码 ```python """ 案例: 演示带参数的多进程. 进程传参有两种方式: 方式1: args方式, 接受所有的 位置参数. 方式2: kwargs方式, 接受所有的 关键字参数. """ # 导包 import multiprocessing, time # 需求: 小明一边敲代码, 一边听音乐. # 1. 定义函数, 表示敲代码. def coding(name, num): for i in range(1, num + 1): time.sleep(0.1) print(f'{name} 正在敲第 {i} 行代码...') # 2. 定义函数, 表示听音乐. def music(name, count): for i in range(1, count + 1): time.sleep(0.1) print(f'{name} 正在听第 {i} 首歌...........') # 3.创建主进程(主线程) if __name__ == '__main__': # 4. 创建两个子进程, 分别关联上述的目标函数. p1 = multiprocessing.Process(target=coding, args=('虚竹', 10)) p2 = multiprocessing.Process(target=music, kwargs={'count': 20, 'name': '刘备'}) # 5. 开启子进程. p1.start() p2.start() ``` ## 获取进程编号 ```python """ 案例: 演示获取进程的编号. 进程的编号解释: 概述: 在设备中, 每个程序(进程)都有自己的唯一进程id, 当程序释放的时候, 该进程id也会释放. 即: 进程id是可以重复使用的. 目的: 1. 查看子进程和父进程的关系, 方便 管理. 2. 例如: 杀死指定进程, 创建子进程... 格式: 查看当前进程的pid: os模块(operating, 系统模块) 的 getpid() get Process id multiprocessing#current_process()的pid属性 查看当前进程的ppid: parent process id(父进程id) os#getppid() 细节: main中创建的进程, 如果没有特殊指定, 它的父进程都是main进程, 而main进程的父进程是 PyCharm程序的pid """ # 导包 import multiprocessing, time import os # 需求: 小明一边敲代码, 一边听音乐. # 1. 定义函数, 表示敲代码. def coding(name, num): for i in range(1, num + 1): time.sleep(0.1) print(f'{name} 正在敲第 {i} 行代码...') print(f'p1进程的pid: {os.getpid()}, {multiprocessing.current_process().pid}, 父进程id(ppid为) : {os.getppid()}') # 2. 定义函数, 表示听音乐. def music(name, count): for i in range(1, count + 1): time.sleep(0.1) print(f'{name} 正在听第 {i} 首歌...........') print(f'p2进程的pid: {os.getpid()}, {multiprocessing.current_process().pid}, 父进程id(ppid为) : {os.getppid()}') # 3.创建主进程(主线程) if __name__ == '__main__': # 4. 创建两个子进程, 分别关联上述的目标函数. p1 = multiprocessing.Process(target=coding, args=('虚竹', 10)) p2 = multiprocessing.Process(target=music, kwargs={'count': 20, 'name': '刘备'}) # 5. 开启子进程. p1.start() p2.start() # 6. 查看主进程的信息. print(f'main进程的pid: {os.getpid()}, {multiprocessing.current_process().pid}, 父进程id(ppid为) : {os.getppid()}') ``` ## 进程特点_数据隔离 * 图解 ![1742205261113](assets/1742205261113.png) * 代码 ```python """ 案例: 演示进程的特点. 进程的特点: 1. 进程之间数据是相互隔离的. 因为子进程相当于是父进程的"副本", 会将父进程的"main外资源"拷贝一份, 即: 各是各的. 2. 默认情况下, 主进程会等待子进程执行结束再结束. """ import multiprocessing import time # 需求: 定义1个公共的容器 my_list = [], 一个进程往里边写数据, 另一个进程从里边读数据, 看是否能读取到. # 1. 定义1个公共的容器 my_list = [] my_list = [] # 2. 定义函数, 往容器中添加数据. def write_data(): for i in range(1, 6): my_list.append(i) print(f'添加数据: {i}') # 走到这里, 说明添加完毕, 打印即可. print(f'write_data函数: {my_list}') # [1, 2, 3, 4, 5] # 3. 定义函数, 从容器中读取数据. def read_data(): time.sleep(3) print(f'read_data函数: {my_list}') # [] print('我是main外资源, 看我执行了几次') # 4. 测试 if __name__ == '__main__': # 5. 创建两个子进程, 分别关联上述的两个函数. p1 = multiprocessing.Process(target=write_data) p2 = multiprocessing.Process(target=read_data) # 6. 启动进程. p1.start() p2.start() # print('我是main内资源, 看我执行了几次') ``` ## 进程特点_守护进程 ```python """ 案例: 演示进程特点之 默认情况下, 主进程会等待子进程执行结束再结束. 进程的特点: 1. 进程之间数据是相互隔离的. 因为子进程相当于是父进程的"副本", 会将父进程的"main外资源"拷贝一份, 即: 各是各的. 2. 默认情况下, 主进程会等待子进程执行结束再结束. 如果要设置主进程结束, 子进程同步结束, 方式如下: 思路1: 设置子进程为 守护进程. 思路2: 强制关闭子进程. 可能会导致子进程变成僵尸进程, 交由Python 解释器自动回收(底层有 init初始化进程来管理维护). """ import multiprocessing import time # 导包 # 1.定义函数, 表示: 子进程的目标函数. def work(): for i in range(10): print('正在努力工作中...') time.sleep(0.2) # 2.测试 if __name__ == '__main__': # 3. 创建子进程, 关联目标函数. # 细节: 进程的默认命名规则是: Process-编号, 编号是从1开始的. # p1 = multiprocessing.Process(target=work, name='刘亦菲') # print(f'p1进程的名字: {p1.name}') p1 = multiprocessing.Process(target=work) # 思路1: 设置p1为: 守护进程. p1.daemon = True # 设置p1为: 守护进程. # 4.启动进程. p1.start() # 5.主进程(main)休眠1秒后, 结束. time.sleep(1) # 思路2: 强制关闭子进程. # p1.terminate() print('main进程结束了.') ``` ## 线程入门 * 无参数 ```python """ 案例: 线程入门案例, 一边听音乐, 一边写代码. 线程的使用步骤: 1. 导包 2. 创建线程对象. 3. 启动线程. 线程和进程的关系: 1. 进程是CPU分配资源的基本单位, 线程是CPU调度资源的最小单位. 2. 线程是依附于进程的, 每个进程至少有1个线程(主线程栈) 3. 进程间数据相互隔离, (同一个进程的)线程间数据可以共享. """ # 导包 import threading, time # 1. 定义函数, 表示: 敲代码. def coding(): for i in range(1, 11): time.sleep(0.1) print(f'正在敲第 {i} 遍代码...') # 2. 定义函数, 表示: 听音乐. def music(): for i in range(1, 11): time.sleep(0.1) print(f'正在听第 {i} 首音乐...') # 3. 测试 if __name__ == '__main__': # 4. 创建两个线程对象, 分别关联上述的两个目标函数. t1 = threading.Thread(target=coding) t2 = threading.Thread(target=music) # 5. 启动线程. t1.start() t2.start() ``` * 带参数 ```python """ 案例: 线程入门案例, 一边听音乐, 一边写代码. 线程的使用步骤: 1. 导包 2. 创建线程对象. 3. 启动线程. 线程和进程的关系: 1. 进程是CPU分配资源的基本单位, 线程是CPU调度资源的最小单位. 2. 线程是依附于进程的, 每个进程至少有1个线程(主线程栈) 3. 进程间数据相互隔离, (同一个进程的)线程间数据可以共享. """ # 导包 import threading, time # 1. 定义函数, 表示: 敲代码. def coding(name, num): for i in range(1, num + 1): time.sleep(0.1) print(f' {name} 正在敲第 {i} 遍代码...') # 2. 定义函数, 表示: 听音乐. def music(name, count): for i in range(1, count + 1): time.sleep(0.1) print(f' {name} 正在听第 {i} 首音乐*********') # 3. 测试 if __name__ == '__main__': # 4. 创建两个线程对象, 分别关联上述的两个目标函数. t1 = threading.Thread(target=coding, args=('李想', 100)) t2 = threading.Thread(target=music, kwargs={'count':50, 'name':'周力'}) # 5. 启动线程. t1.start() t2.start() ``` ## 多线程特点_随机性 ```python """ 案例: 演示多线程特点. 多线程特点: 1. 线程执行具有随机性, 原因是因为CPU在做着高效的切换. 2. 默认情况下, 主线程会等待子线程结束再结束. 3. (同一个进程的)线程间 数据共享。 4. 多线程操作共享数据, 可能会出现安全问题, 可以用 互斥锁解决。 CPU调度资源的策略: 1.均分时间片 2.抢占式调度 """ # 需求: 创建多个线程, 多次运行, 观察结果. # 导包 import threading import time # 1.定义多线程的目标函数. def print_info(): # 1.1 休眠 time.sleep(0.2) # 1.2 获取当前线程对象. current_thread = threading.current_thread() # 1.3 打印当前线程的名字. print(current_thread.name) # 2. 测试 if __name__ == '__main__': # 2.1 创建10个线程, 观察其运行效果. for i in range(10): t = threading.Thread(target=print_info) t.start() ``` ## 多线程特点_守护线程 ```python """ 案例: 演示多线程特点之 守护线程. 多线程特点: 1. 线程执行具有随机性, 原因是因为CPU在做着高效的切换. 2. 默认情况下, 主线程会等待子线程结束再结束. 3. (同一个进程的)线程间 数据共享。 4. 多线程操作共享数据, 可能会出现安全问题, 可以用 互斥锁解决。 """ # 导包 import threading, time # 1.定义目标函数. def work(): for i in range(10): time.sleep(0.2) print('工作中...') # 2. 测试. if __name__ == '__main__': # 2.1 创建(子)线程对象. # (守护线程)写法1: daemon属性 # t = threading.Thread(target=work, daemon=True) # (守护线程)写法2: setDaemon()函数, 已过时(暂时还支持, 以后的新版本中可能会被移除掉). # t = threading.Thread(target=work) # t.setDaemon(True) # (守护线程)写法3: daemon属性 t = threading.Thread(target=work) t.daemon = True # 2.2 启动线程. t.start() # 2.3 设置主线程休眠时间1秒 time.sleep(1) # 2.4 设置主线程的结束标记. print('主线程结束了!') ``` ## 多线程特点_数据共享 ```python """ 案例: 演示多线程特点之 数据共享. 多线程特点: 1. 线程执行具有随机性, 原因是因为CPU在做着高效的切换. 2. 默认情况下, 主线程会等待子线程结束再结束. 3. (同一个进程的)线程间 数据共享。 4. 多线程操作共享数据, 可能会出现安全问题, 可以用 互斥锁解决。 """ # 需求: 定义全局变量my_list = [], 定义两个目标函数分别实现添加, 查看数据. 最后创建两个线程, 分别执行对应的任务, 观察结果. # 导包 import threading, time # 1.定义全局变量 my_list = [] # 2. 定义目标函数, 添加数据. def write_data(): for i in range(1, 6): my_list.append(i) print("写入数据: ", i) print(f'write_data函数: {my_list}') # 3. 定义目标函数, 查看数据. def read_data(): # 休眠, 即: 等待write_data()执行结束在结束. time.sleep(2) print(f'read_data函数: {my_list}') # 4. 测试 if __name__ == '__main__': # 4.1 创建线程对象, 并且启动线程. t1 = threading.Thread(target=write_data) t2 = threading.Thread(target=read_data) # 4.2 启动线程. t1.start() t2.start() ``` ## 多线程特点_互斥锁 * 图解 ![1742352980442](assets/1742352980442.png) * 代码 ```python """ 案例: 演示多线程共享全局变量, 可能出现的问题. 多线程共享全局变量, 出现问题的问题: 累加次数不够. 产生原因: 线程1还没有来记得执行完(一个完整的动作)前, 被线程2抢走了资源, 就可能出问题. 解决方案: 加锁思想, 即: 互斥锁. 细节: 使用互斥锁的时候, 要在合适的时机释放所, 否则可能出现 死锁 或者 锁不住的情况. """ # 需求: 定义两个函数, 分别对全局变量累加100W次, 创建两个线程, 关联这两个函数, 执行看效果. # 导包 import threading # 1.定义全局变量. global_num = 0 # 创建线程锁. mutex = threading.Lock() # mutex2 = threading.Lock() # 2.定义目标函数1, 对全局变量累加100W次. def target_fun1(): mutex.acquire() # 加锁 # 2.1 声明为全局变量 global global_num # 2.2 遍历100W次, 对全局变量进行累加. for i in range(1000000): # 2.3 具体的累加动作 global_num += 1 # 2.4 累加完毕后, 打印结果. print(f'target_fun1函数结果: {global_num}') mutex.release() # 释放锁 # 3.定义目标函数2, 对全局变量累加100W次. def target_fun2(): mutex.acquire() # 加锁 global global_num for i in range(1000000): global_num += 1 print(f'target_fun2函数结果: {global_num}') mutex.release() # 释放锁 # 4.测试. if __name__ == '__main__': # 4.1 创建两个线程, 分别关联上述的两个目标函数. t1 = threading.Thread(target=target_fun1) t2 = threading.Thread(target=target_fun2) # 4.2 开启线程. t1.start() t2.start() ``` ## 进程和线程对比 > ```python > 进程和线程的区别: > 1. 线程依赖进程, 进程是CPU分配资源的基本单位, 线程是CPU调度资源的基本单位. > 2. 进程更消耗资源, 不能共享全局变量, 相对更稳定. > 3. 线程更轻量级, 可以共享全局变量, 相对更灵活. > ``` ## 迭代器入门 ```python """ 案例: 演示自定义迭代器. 迭代器介绍: 概述: 自定义的类, 只要重写了 __iter__() 和 __next__() 方法, 就可以称为 迭代器. 目的: 隐藏底层的逻辑, 让用户使用更方便. 惰性加载, 用的时候才会获取. 回顾: for循环格式 for i in 可迭代类型: pass """ # 需求: 模拟range(1, 6), 自定义 迭代器实现同等逻辑. # 场景1: 回顾 range()用法. for i in range(1, 6): print(i) print('-' * 23) # 场景2: 自定义迭代器. # 1. 自定义 迭代器类. class MyIterator: # 2. 通过init魔法方法, 初始化属性, 指定: 范围. def __init__(self, start, end): self.current_value = start # 当前值, 默认为 开始值. self.end = end # 结束值. # 3. 重写iterator魔法方法, 返回当前对象(即: 迭代器对象). def __iter__(self): return self # 4. 重写next魔法方法, 返回当前值, 并更新当前值. def __next__(self): # 4.1 判断当前值范围是否合法. if self.current_value >= self.end: raise StopIteration # 抛出异常, 迭代结束. # 4.2 走这里, 说明当前值合法, 返回当前值, 并更新当前值. # value = self.current_value # value = 1 2 3 4 5 # self.current_value += 1 # self.current_value = 2 3 4 5 6 # return value # 1 2 3 4 5 # 效果同上, 代码更简单 self.current_value += 1 # self.current_value = 2 3 4 5 6 return self.current_value - 1 # 1 2 3 4 5 # 5. 创建迭代器对象, 并遍历. # 5.1 for循环 for i in MyIterator(1, 6): print(i) print('-' * 23) # 5.2 next()函数 my_itr = MyIterator(10, 13) print(next(my_itr)) # 10 print(next(my_itr)) # 11 print(next(my_itr)) # 12 # print(next(my_itr)) # raise StopIteration # 抛出异常, 迭代结束. ``` ## 生成器介绍 * **案例1: 推导式写法** ```python """ 案例: 演示生成器之 推导式写法. 生成器介绍: 概述: 所谓的生成器就是基于 数据规则, 用一部分在生成一部分, 而不是一下子生成完所有. 目的: 可以节省大量的内存. 实现方式: 1. 推导式写法. 2. yield关键字 """ import sys # system: 系统模块 # 场景1: 生成器 推导式写法. # 需求1: 生成1 ~ 10之间的整数. my_generator = (i for i in range(1, 11)) print(my_generator) print(type(my_generator)) # print('-' * 23) # 需求2: 生成 1 ~ 10 之间的偶数. my_gt2 = (i for i in range(1, 11) if i % 2 == 0) print(my_gt2) print('-' * 23) # 需求3: 如何从生成器中获取数据. # 思路1: next() print(next(my_gt2)) # 2 print(next(my_gt2)) # 4 print('*' * 23) for i in my_gt2: print(i) # 6, 8, 10 print('-' * 23) # 验证 生成器的目的 就是可以减少内存占用. my_list = [i for i in range(1000000)] my_gt3 = (i for i in range(1000000)) print(type(my_list), type(my_gt3)) # 查看my_list的内存空间占用. print(f'my_list的内存占用: {sys.getsizeof(my_list)}') # 89095160 print(f'my_gt3的内存占用: {sys.getsizeof(my_gt3)}') # 192 print('-' * 23) ``` * **案例2: yield关键字** ```python """ 案例: 演示生成器之 推导式写法. 生成器介绍: 概述: 所谓的生成器就是基于 数据规则, 用一部分在生成一部分, 而不是一下子生成完所有. 目的: 可以节省大量的内存. 实现方式: 1. 推导式写法. 2. yield关键字 """ # 需求: 通过yield方式, 获取到生成器之 1 ~ 10之间的整数. # 回顾: 推导式写法. my_g = (i for i in range(1, 11)) # yield方式如下. # 1.定义函数, 存储到生成器中, 并返回. def my_fun(): # my_list = [] # 创建 # for i in range(1, 11): # my_list.append(i) # 添加 # return my_list # 返回 # 效果类似于上边的代码. # yield在这里做了三件事儿: 1.创建生成器对象. 2.把值存储到生成器中. 3.返回生成器. for i in range(1, 11): yield i # 2.测试. my_g2 = my_fun() print(type(my_g2)) # print(next(my_g2)) print(next(my_g2)) print('-' * 23) for i in my_g2: print(i) ``` * **案例3: 批量歌词** ```python """ 案例: 基于传入的数值(每批次的歌词条数), 创建 生成器, 生成批次歌词. """ import math # 需求: 基于文件中 周杰伦的歌词, 创建生成器, 根据传入的每批次的歌词条数, 生成歌词批次. # 1. 定义函数, 接收 每批次的歌词条数, 返回生成器. def dataset_loader(batch_size): # 假设是 8条/批次 """ 自定义的 歌词 批量生成器 :param batch_size: 每批次的歌词条数 :return: 生成器, 每个元素都是一批次的数据, 例如: (8条, 8条, 8条...) """ # 1.1 读取文件数据. with open('./data/jaychou_lyrics.txt', 'r', encoding='utf-8') as src_f: # 1.2 一次读取所有行. # lines = [line.strip() for line in src_f.readlines()] lines = src_f.readlines() # 1.3 计算批次总数, 假设: 5批 total_batch = math.ceil(len(lines) / batch_size) # 1.4 for循环方式, 获取到每批次的数据, 放到生成器中, 并返回. for idx in range(total_batch): # idx的值: 0, 1, 2, 3, 4 # 第1批歌词, 批次索引(idx=0), 歌词为: 第1条 ~ 第8条, 索引为: 0 ~ 7 # 第2批歌词, 批次索引(idx=1), 歌词为: 第9条 ~ 第16条, 索引为: 8 ~ 15 # 第3批歌词, 批次索引(idx=2), 歌词为: 第17条 ~ 第24条, 索引为: 16 ~ 23 yield lines[idx * batch_size : idx * batch_size + batch_size] # 第1批 # 2. 测试. dl = dataset_loader(8) print(next(dl)) # 第1批 print(next(dl)) # 第2批 for batch_data in dl: print(batch_data) ``` ## Property属性介绍 * **场景1: 装饰器用法** ```python """ 案例: 演示property属性的用法. property属性介绍: 概述/目的/作用: 把 函数 当做 变量来使用. 实现方式: 方式1: 装饰器. 方式2: 类属性. property的装饰器用法: @property 修饰 获取值的函数 @获取值的函数名.setter 修饰 设置值的函数 之后, 就可以直接 .上述的函数名 来当做变量直接用. """ # 需求: 定义学生类, 私有属性 age, 通过property实现简化调用. # 1. 定义学生类. class Student: # 1.1 私有属性. def __init__(self): self.__age = 18 # 1.2 提供公共的方式方式 @property def age(self): return self.__age @age.setter def age(self, age): # 可以在这里对传入的age值做判断, 但是一般不做, 重要字段才会做判断. # 因为实际开发中数据是从前端传过来的, 已经做过判断了, 这里做属于二次校验. self.__age = age # 2. 测试 if __name__ == '__main__': # 2.1 创建学生对象. s = Student() # 2.2. 设置值 s.age = 20 # 2.3 获取值 print(s.age) ``` * **场景2: 类属性** ```python """ 案例: 演示property属性的用法. property属性介绍: 概述/目的/作用: 把 函数 当做 变量来使用. 实现方式: 方式1: 装饰器. 方式2: 类属性. property的装饰器用法: @property 修饰 获取值的函数 @获取值的函数名.setter 修饰 设置值的函数 property类属性的用法: 类属性名 = property(获取值的函数名, 设置值的函数名) 之后, 就可以直接 .上述的函数名 来当做变量直接用. """ # 需求: 定义学生类, 私有 age属性, 通过property充当类属性用. # 1. 定义学生类. class Student: # 1.1 私有age属性. def __init__(self): self.__age = 20 # 1.2 公共的访问方式. def get_age(self): return self.__age def set_age(self, age): self.__age = age # 1.3 封装上述的公共方式为 类属性 # 参1: 获取值的函数名, 参2: 设置值的函数名 age = property(get_age, set_age) # 2. 测试 if __name__ == '__main__': # 2.1 创建学生对象. s = Student() # 2.2. 设置值 s.age = 99 # 2.3 获取值 print(s.age) ``` ## 正则表达式入门 ```python """ 案例: 演示正则表达式之 校验单个字符. 正则表达式介绍: 概述: 正确的, 符合特定规则的 字符串. Regular Expression, 正则表达式, 简称: re 细节: 1. 学正则表达式, 就是学正则表达式的规则, 你用不背, 网上一搜一大堆. 2. 关于正则我对大家的要求是, 能用我们讲的规则, 看懂别人写的式子, 且会简单修改即可. 3. 正则不独属于Python, 像Java, JavaScript, PHP, Go等都支持. 步骤: 1. 导包 import re 2. 正则匹配 result = re.match('正则表达式', '要校验的字符串') 从前往后依次匹配,只要能匹配即可. result = re.search('正则表达式', '要校验的字符串') 分段查找. 3. 获取匹配结果. result.group() 正则常用的规则: . 代表任意的 1个字符, 除了 \n \. 取消.的特殊含义, 就是1个普通的. a 代表1个普通的字符 a [abc] 代表a,b,c中任意的1个字符 [^abc] 代表除了a,b,c外, 任意的1个字符 \d 代表数字, 等价于 [0-9] \D 代表非数字, 等价于 [^0-9] \s \S \w \W ^ $ * ? + {n} {n,} {n,m} | 代表 或者的意思 () \num 扩展: (?P<分组名>) (?P=分组名) """ # 需求: 正则入门. # 1.导包 import re # 2.正则校验, 参1: 正则规则, 参2: 要被校验的字符串 # result = re.match('.it', 'ait') # 匹配成功 # result = re.match('.it', '你it') # 匹配成功 # result = re.match('.it', '你好it') # 失败 # result = re.match('\.it', '你it') # 失败 # result = re.match('\.it', '.it') # 匹配成功 result = re.match('[ahg]it', 'ait') # 匹配成功 result = re.match('[ahg]it', 'hit') # 匹配成功 result = re.match('[ahg]it', 'git') # 匹配成功 result = re.match('[ahg]it', 'g it') # 失败 result = re.match('[^ahg]it', 'ait') # 失败 result = re.match('[^ahg]it', 'x it') # 失败 result = re.match('[^ahg]it', 'xit') # 匹配成功 result = re.match('[^ahg]it', 'xitabcxyz') # 匹配成功, 从前往后匹配, 匹配到就返回. result = re.match('[^ahg]it', 'abcxitabcxyz') # 失败, 从前往后依次查找. # result = re.search('[^ahg]it', 'abcxitabcxyz') # 失败, 从前往后依次查找. result = re.match('[3-7]it', '3it') # 匹配成功 result = re.match('[3-7]it', '-it') # 失败, [3-7]等价于[34567] # 3.获取匹配结果. if result: print(result.group()) else: print('匹配失败') ``` ## 正则替换 ```python """ 案例: 演示正则替换. 回顾正则的使用步骤: 1. 导包 import re 2. 正则匹配 result = re.match('正则表达式', '要校验的字符串') 从前往后依次匹配,只要能匹配即可. result = re.search('正则表达式', '要校验的字符串') 分段查找. result = re.compile('正则表达式').sub(替换后的内容, 要被替换的字符串) 替换 3. 获取匹配结果. result.group() """ # 导包 import re # 1.定义字符串. s = '开心你就大声笑,哈哈,呵呵,嘿嘿,嘻嘻,桀桀桀,啦啦啦夯' # 2.把上述的 哈,呵,嘿,嘻,桀 替换为 ♥ # 正则规则 新字符串 要被替换的字符串 result = re.compile('哈|呵|嘿|嘻|桀').sub('♥', s) # 3.打印结果. print(result) print('-' * 23) # 新版API(函数)的写法. # 参1: 正则规则, 参2: 新字符串, 参3: 要被替换的字符串 result = re.sub('哈|呵|嘿|嘻|桀', '♣', s) print(result) ``` ## 正则表达式_校验单个字符 ```python """ 案例: 演示正则表达式之 校验单个字符. 正则表达式介绍: 概述: 正确的, 符合特定规则的 字符串. Regular Expression, 正则表达式, 简称: re 细节: 1. 学正则表达式, 就是学正则表达式的规则, 你用不背, 网上一搜一大堆. 2. 关于正则我对大家的要求是, 能用我们讲的规则, 看懂别人写的式子, 且会简单修改即可. 3. 正则不独属于Python, 像Java, JavaScript, PHP, Go等都支持. 步骤: 1. 导包 import re 2. 正则匹配 result = re.match('正则表达式', '要校验的字符串') 从前往后依次匹配,只要能匹配即可. result = re.search('正则表达式', '要校验的字符串') 分段查找. 3. 获取匹配结果. result.group() 正则常用的规则: . 代表任意的 1个字符, 除了 \n \. 取消.的特殊含义, 就是1个普通的. a 代表1个普通的字符 a [abc] 代表a,b,c中任意的1个字符 [^abc] 代表除了a,b,c外, 任意的1个字符 \d 代表数字, 等价于 [0-9] \D 代表非数字, 等价于 [^0-9] \s 代表空白字符, 等价于 [\t\n\r] \S 代表非空白字符 \w 代表非特殊字符, 即: 数字, 字母, 下划线, 汉字, [a-zA-Z0-9_汉字] \W 代表特殊字符, 非字母,数字,下划线,汉字 ^ $ * ? + {n} {n,} {n,m} | 代表 或者的意思 () \num 扩展: (?P<分组名>) (?P=分组名) """ # 需求: 正则入门. # 1.导包 import re # 2.正则校验, 参1: 正则规则, 参2: 要被校验的字符串 # result = re.match('.it', 'ait') # 匹配成功 # result = re.match('.it', '你it') # 匹配成功 # result = re.match('.it', '你好it') # 失败 # result = re.match('\.it', '你it') # 失败 # result = re.match('\.it', '.it') # 匹配成功 result = re.match('[ahg]it', 'ait') # 匹配成功 result = re.match('[ahg]it', 'hit') # 匹配成功 result = re.match('[ahg]it', 'git') # 匹配成功 result = re.match('[ahg]it', 'g it') # 失败 result = re.match('[^ahg]it', 'ait') # 失败 result = re.match('[^ahg]it', 'x it') # 失败 result = re.match('[^ahg]it', 'xit') # 匹配成功 result = re.match('[^ahg]it', 'xitabcxyz') # 匹配成功, 从前往后匹配, 匹配到就返回. result = re.match('[^ahg]it', 'abcxitabcxyz') # 失败, 从前往后依次查找. # result = re.search('[^ahg]it', 'abcxitabcxyz') # 失败, 从前往后依次查找. result = re.match('[3-7]it', '3it') # 匹配成功 result = re.match('[3-7]it', '-it') # 失败, [3-7]等价于[34567] result = re.match('a\\dhm', 'a1hm') # 匹配成功 result = re.match('a\\dhm', 'a10hm') # 失败 result = re.match('a\\Dhm', 'a!hm') # 匹配成功 result = re.match('a\\Dhm', 'abhm') # 匹配成功 result = re.match('a\\shm', 'abhm') # 失败 result = re.match('a\\shm', 'a\thm') # 匹配成功 result = re.match('a\\shm', 'a\nhm') # 匹配成功 result = re.match('a\\shm', 'a hm') # 匹配成功 result = re.match('a\\whm', 'a\thm') # 失败 result = re.match('a\\whm', 'a!hm') # 失败 result = re.match('a\\whm', 'axhm') # 匹配成功 result = re.match('a\\whm', 'a_hm') # 匹配成功 result = re.match('a\\whm', 'a6hm') # 匹配成功 result = re.match('a\\whm', 'aYhm') # 匹配成功 result = re.match('a\\whm', 'a夯hm') # 匹配成功 # 3.获取匹配结果. if result: print(result.group()) else: print('匹配失败') ``` ## 正则表达式_校验多个字符 ```python """ 案例: 演示正则表达式之 校验单个字符. 正则表达式介绍: 概述: 正确的, 符合特定规则的 字符串. Regular Expression, 正则表达式, 简称: re 细节: 1. 学正则表达式, 就是学正则表达式的规则, 你用不背, 网上一搜一大堆. 2. 关于正则我对大家的要求是, 能用我们讲的规则, 看懂别人写的式子, 且会简单修改即可. 3. 正则不独属于Python, 像Java, JavaScript, PHP, Go等都支持. 步骤: 1. 导包 import re 2. 正则匹配 result = re.match('正则表达式', '要校验的字符串') 从前往后依次匹配,只要能匹配即可. result = re.search('正则表达式', '要校验的字符串') 分段查找. 3. 获取匹配结果. result.group() 正则常用的规则:9 . 代表任意的 1个字符, 除了 \n \. 取消.的特殊含义, 就是1个普通的. a 代表1个普通的字符 a [abc] 代表a,b,c中任意的1个字符 [^abc] 代表除了a,b,c外, 任意的1个字符 \d 代表数字, 等价于 [0-9] \D 代表非数字, 等价于 [^0-9] \s 代表空白字符, 等价于 [\t\n\r] \S 代表非空白字符 \w 代表非特殊字符, 即: 数字, 字母, 下划线, 汉字, [a-zA-Z0-9_汉字] \W 代表特殊字符, 非字母,数字,下划线,汉字 ^ $ * 代表前边的内容 出现至少0次, 至多无数次 ? 代表前边的内容 出现至少0次, 至多1次 + 代表前边的内容 出现至少1次, 至多无数次 {n} 代表前边的内容 恰好出现n次, 多一次,少一次都不行 {n,} 代表前边的内容 至少出现n次, 至多无数次 {n,m} 代表前边的内容 至少出现n次, 至多出现m次, 包左包右. | 代表 或者的意思 () \num 扩展: (?P<分组名>) (?P=分组名) """ # 导包 import re # 验证 * 代表前边的内容 出现至少0次, 至多无数次 result = re.match('.*hm.*', 'abchm123') # 匹配成功 result = re.match('.*hm.*', 'hm123') # 匹配成功 result = re.match('.*hm.*', 'abchm') # 匹配成功 result = re.match('.+hm.*', 'abchm') # 匹配成功 result = re.match('.+hm.*', 'hm123') # 失败 result = re.match('.?hm.*', 'ahm123') # 匹配成功 result = re.match('.?hm.*', 'hm123') # 匹配成功 result = re.match('.?hm.*', 'abchm123') # 失败 result = re.match(r'\d{3}hm\w{2,5}', '123hm123') # 匹配成功 result = re.match(r'\d{3}hm\w{2,5}', '123hm12@') # 匹配成功 result = re.match(r'\d{3}hm\w{2,5}', '123hmabcAB') # 匹配成功 result = re.match(r'\d{3}hm\w{2,5}', '1234hm123') # 失败 result = re.match(r'\d{3}hm\w{2,5}', '12hm123') # 失败 result = re.match(r'\d{3}hm\w{2,5}', '123hm1@') # 失败 result = re.match(r'\d{3}hm\w{2,5}', '123hmabcAB1') # 失败 result = re.match(r'\d{3,}hm\w{2,5}', '12hmabcAB1') # 失败 result = re.match(r'\d{3,}hm\w{2, 5}', '123hmabcAB1') # 失败, 注意空格 result = re.match(r'\d{3,}hm\w{2,5}', '123hmabc') # 匹配成功 # 验证 ? 代表前边的内容 出现至少0次, 至多1次 # 验证 + 代表前边的内容 出现至少1次, 至多无数次 # 验证 {n} 代表前边的内容 恰好出现n次, 多一次,少一次都不行 # 验证 {n,} 代表前边的内容 至少出现n次, 至多无数次 # 验证 {n,m} 代表前边的内容 至少出现n次, 至多出现m次, 包左包右. # 查看结果. print(result.group() if result else '未匹配') ``` ## 正则表达式_校验开头和结尾 ```python r""" 案例: 演示正则表达式之 校验单个字符. 正则表达式介绍: 概述: 正确的, 符合特定规则的 字符串. Regular Expression, 正则表达式, 简称: re 细节: 1. 学正则表达式, 就是学正则表达式的规则, 你用不背, 网上一搜一大堆. 2. 关于正则我对大家的要求是, 能用我们讲的规则, 看懂别人写的式子, 且会简单修改即可. 3. 正则不独属于Python, 像Java, JavaScript, PHP, Go等都支持. 步骤: 1. 导包 import re 2. 正则匹配 result = re.match('正则表达式', '要校验的字符串') 从前往后依次匹配,只要能匹配即可. result = re.search('正则表达式', '要校验的字符串') 分段查找. 3. 获取匹配结果. result.group() 正则常用的规则:9 . 代表任意的 1个字符, 除了 \n \. 取消.的特殊含义, 就是1个普通的. a 代表1个普通的字符 a [abc] 代表a,b,c中任意的1个字符 [^abc] 代表除了a,b,c外, 任意的1个字符 \d 代表数字, 等价于 [0-9] \D 代表非数字, 等价于 [^0-9] \s 代表空白字符, 等价于 [\t\n\r] \S 代表非空白字符 \w 代表非特殊字符, 即: 数字, 字母, 下划线, 汉字, [a-zA-Z0-9_汉字] \W 代表特殊字符, 非字母,数字,下划线,汉字 ^ 表示开头 $ 表示结尾 * 代表前边的内容 出现至少0次, 至多无数次 ? 代表前边的内容 出现至少0次, 至多1次 + 代表前边的内容 出现至少1次, 至多无数次 {n} 代表前边的内容 恰好出现n次, 多一次,少一次都不行 {n,} 代表前边的内容 至少出现n次, 至多无数次 {n,m} 代表前边的内容 至少出现n次, 至多出现m次, 包左包右. | 代表 或者的意思 () \num 扩展: (?P<分组名>) (?P=分组名) """ # 导包 import re # 正则匹配 # 需求1: 校验字符串必须以数字开头, 无论match(), 还是search()均是. 后边是啥无所谓. # result = re.match(r'\d+.*', 'abc123xyz') # 失败 # result = re.search(r'\d+.*', 'abc123xyz') # 匹配成功 # # result = re.match(r'^\d+.*', 'abc123xyz') # 失败 # result = re.search(r'^\d+.*', 'abc123xyz') # 失败 # 需求2: 校验字符串必须以数字开头, 以任意的3个字母结尾. # result = re.search(r'^\d+.*[a-zA-Z]{3}', 'abc123xyz12') # 失败 # result = re.search(r'^\d+.*[a-zA-Z]{3}', '123你好xyz12') # 匹配成功 # result = re.search(r'^\d+.*[a-zA-Z]{3}$', '123你好abc12') # 失败 # result = re.search(r'^\d+.*[a-zA-Z]{3}$', '123你好abc') # 匹配成功 # 需求3: 校验手机号. 规则: 1.长度必须是11位 2.必须是纯数字. 3.第1位数字必须是1. 4.第2位数字可以是 3-9 result = re.match(r'^1[3-9]\d{9}$', '13112345678a') result = re.match(r'^1[3-9]\d{9}$', '12112345678') result = re.match(r'^1[3-9]\d{9}$', '13112345678') # 打印匹配结果. print(result.group() if result else '未匹配!') ``` ## 正则表达式_校验分组 ```python r""" 案例: 演示正则表达式之 校验分组. 正则表达式介绍: 概述: 正确的, 符合特定规则的 字符串. Regular Expression, 正则表达式, 简称: re 细节: 1. 学正则表达式, 就是学正则表达式的规则, 你用不背, 网上一搜一大堆. 2. 关于正则我对大家的要求是, 能用我们讲的规则, 看懂别人写的式子, 且会简单修改即可. 3. 正则不独属于Python, 像Java, JavaScript, PHP, Go等都支持. 步骤: 1. 导包 import re 2. 正则匹配 result = re.match('正则表达式', '要校验的字符串') 从前往后依次匹配,只要能匹配即可. result = re.search('正则表达式', '要校验的字符串') 分段查找. 3. 获取匹配结果. result.group() 正则常用的规则:9 . 代表任意的 1个字符, 除了 \n \. 取消.的特殊含义, 就是1个普通的. a 代表1个普通的字符 a [abc] 代表a,b,c中任意的1个字符 [^abc] 代表除了a,b,c外, 任意的1个字符 \d 代表数字, 等价于 [0-9] \D 代表非数字, 等价于 [^0-9] \s 代表空白字符, 等价于 [\t\n\r] \S 代表非空白字符 \w 代表非特殊字符, 即: 数字, 字母, 下划线, 汉字, [a-zA-Z0-9_汉字] \W 代表特殊字符, 非字母,数字,下划线,汉字 ^ 表示开头 $ 表示结尾 * 代表前边的内容 出现至少0次, 至多无数次 ? 代表前边的内容 出现至少0次, 至多1次 + 代表前边的内容 出现至少1次, 至多无数次 {n} 代表前边的内容 恰好出现n次, 多一次,少一次都不行 {n,} 代表前边的内容 至少出现n次, 至多无数次 {n,m} 代表前边的内容 至少出现n次, 至多出现m次, 包左包右. | 代表 或者的意思 () 代表 分组, 从左往右数, 第几个左小括号(, 就表示第几组 \num 代表 引用第几组的内容. 扩展: (?P<分组名>) 设置分组 (?P=分组名) 使用分组 """ # 导包 import re # 需求: 在列表 fruits = ['apple', 'banana', 'orange', 'pear'], 匹配 apple, pear # 1.定义水果列表 fruits = ['apple', 'banana', 'orange', 'pear'] # 2. 遍历, 获取到每种水果. for fruit in fruits: # 3. 判断当前水果是否是 喜欢吃的水果. # 参1: 正则表达式, 参2: 要校验的字符串. if re.match('apple|pear', fruit): # 4. 走这里, 说明是喜欢吃的. print(f'喜欢吃: {fruit}') else: # 5. 走这里, 说明不是喜欢吃的. print(f'不喜欢吃: {fruit}') ``` ## 正则表达式_校验邮箱 ```python r""" 案例: 演示正则表达式之 校验邮箱. 正则规则: | 代表 或者的意思 () 代表 分组, 从左往右数, 第几个左小括号(, 就表示第几组 \num 代表 引用第几组的内容. 扩展: (?P<分组名>) 设置分组 (?P=分组名) 使用分组 """ # 导包 import re # 1. 定义邮箱. email = "abcd@163.com" # 2. 校验邮箱是否合法. result = re.match(r'^[a-zA-Z_0-9]{4,20}@(163|126|qq)\.com$', email) # 3. 打印结果. if result: print(f'合法邮箱为: {result.group()}') print(f'合法邮箱为: {result.group(0)}') # 获取第0组的信息, 效果同上, 即: 整个匹配到的结果. print(f'合法邮箱为: {result.group(1)}') # 获取第1组的信息, 即: 163 else: print("邮箱不合法!") ``` ## 正则表达式_提取QQ号 ```python r""" 案例: 演示正则表达式之 校验邮箱. 正则规则: | 代表 或者的意思 () 代表 分组, 从左往右数, 第几个左小括号(, 就表示第几组 \num 代表 引用第几组的内容. 扩展: (?P<分组名>) 设置分组 (?P=分组名) 使用分组 """ import re # 需求: 数据格式为 qq:数字, 从中提qq文本 和 qq号 # 1.定义变量, 记录要校验的字符串. s = 'qq:123456' # 2.正则校验. result = re.match(r'^(qq):(\d{6,11})$', s) # 3.提取内容. if result: print(result.group()) print(result.group(0)) # 效果同上. print('-' * 23) print(result.group(1)) print(result.group(2)) else: print('未匹配') ``` ## 正则表达式_校验html ```python r""" 案例: 演示正则表达式之 校验邮箱. 正则规则: | 代表 或者的意思 () 代表 分组, 从左往右数, 第几个左小括号(, 就表示第几组 \num 代表 引用第几组的内容. 扩展: (?P<分组名>) 设置分组 (?P=分组名) 使用分组 参考的html代码: # 开始, 开放标签, 结束, 闭合标签.
# 自闭合标签. """ import re # 需求1: 校验html的单级标签. # 1.定义变量, 记录: html标签. # html_s = '我是html页面' # 字母数: 1 ~ 4 # 2.匹配校验. # 写法1: 重新copy一份. # result = re.match('<[a-zA-Z]{1,4}>.*', html_s) # 写法2: 引入分组的概念. # result = re.match(r'<([a-zA-Z]{1,4})>.*', html_s) # 3.打印结果. # if result: # print(result.group()) # else: # print('未匹配!') # 需求2: 校验html的单级标签. # 1.定义变量, 记录: html标签. html_s = '

我是html页面

' # 字母数: 1 ~ 4, 标题标签1 ~ 6 # 2.匹配校验. # 写法1: 重新copy一份. # result = re.match(r'<[a-zA-Z]{1,4}>.*', html_s) # 写法2: 引入分组的概念. # result = re.match(r'<([a-zA-Z]{1,4})><(h[1-6])>.*', html_s) # 写法3: 给分组起名. result = re.match(r'<(?P[a-zA-Z]{1,4})><(?Ph[1-6])>.*', html_s) # 3.打印结果. if result: print(result.group()) else: print('未匹配!') ``` ## 数据结构和算法简介 * 数据结构 > 就是存储和组织数据的方式, 分为: **线性结构** 和 **非线性结构** * 算法 > 就是解决问题的思路和发放, 它具有独立性, 即: 它不依赖语言, 而是解决问题的思路. Java能做, Python也能做. * 特性 > 有输入, 有输出, 有穷性, 确定性, 可行性. * 如何衡量算法的优劣 > ==**大O标记法,**== 即: 将次要条件都省略掉, 最终形成1个表达式. > > **主要条件:**随着问题规模变化而==**变化**==的. > > **次要条件:**随则问题规模变化而==**不变**==的. ![1742438995269](assets/1742438995269.png) * 最优和最坏时间复杂度 > 如非特殊说明, 我们考虑的都是 **最坏时间复杂度**, 因为它是算法的一种保证. > > 而最优时间复杂度是算法的 最理想, 最乐观的状况, 没有太大的参考价值. * 常见的时间复杂度如下 > **从最优到最坏分别是:** > > O(1) -> O(logn) -> O(n) -> O(n logn) -> O(n²) -> O(n³) > > 常数阶 -> 对数阶 -> 线性阶 -> 线性对数阶 -> 平方阶 -> 立方阶 * 常见的空间复杂度如下 > **了解即可, 因为服务器(内存)资源一般是足够的** > > **从最优到最坏分别是:** > > O(1) -> O(logn) -> O(n) -> O(n²) -> O(n³) ## 数据结构分类 * 图解 ![1742443857120](assets/1742443857120.png) * 分类 > 数据结构 = 存储, 组织数据的方式, 是算法解决问题时的载体. * 线性结构 > **特点:** 每个节点都只能有1个前驱, 1个后继节点. > > **例如:** 顺序表(栈, 队列), 链表 * 非线性结构 > **特点:** 每个节点都可以有多个前驱, 多个后继节点. > > **例如:** 树, 图 ## 线性结构存储数据的方式 * 图解 ![1742449680820](assets/1742449680820.png) * 顺序表存储方式详解 * 一体式存储 ![1742449752895](assets/1742449752895.png) * 分离式存储 ![1742449775956](assets/1742449775956.png) ## 顺序表的存储方式 * 解释 顺序表有 数据区 和 信息区两部分组成. * 特点 * 数据区 和 信息区在一起的 -> 一体式存储(**扩容时只能整体搬迁**) * 数据取货 和 信息区分开存储的 -> 分离式存储(**可以直接让信息区指向新的 数据区即可, 不用整体搬迁**). * 顺序表扩容策略 > 思路1: 每次增加固定的容量. **拿时间换空间** > 思路2: 每次扩容, 容量翻倍. **拿空间换时间** ## 线性结构之链表介绍 * 概述 > 链表属于 线性结构, 在存储时 不要求连续的内存空间, 只要有地儿就行. > 可以简单理解为, 它是用来解决顺序表的弊端的(必须要有足够的连续空间, 否则扩容失败.) * 组成 > 链表 由 节点 组成. 节点又分为 数值域(元素域) 和 地址域(链接域), 根据节点不同, 链表可以分为 四大类. * 图解 ![1742458618651](assets/1742458618651.png) ![1742458631223](assets/1742458631223.png) ## 自定义代码模拟链表 ```python """ 案例: 自定义代码模拟链表 链表介绍: 概述: 它属于数据结构之 线性结构的一种, 每个节点都只能有 1个前驱 和 1个后继节点. 作用: 用于优化顺序表的弊端(如果没有足够的连续的内存空间, 会导致扩容失败) 链表扩容时, 有地儿就行, 连不连续无所谓. 组成: 由 节点 组成, 其中节点由 元素域(数值域) 和 链接域(地址域)组成. 分类: 根据 节点类型不同, 链表主要分为: 单向链表: 节点由1个数值域 和 1个地址域组成, 前边节点的地址域存储的是后续节点的地址, 最后1个节点的地址域为 None 单向循环链表: 双向链表: 双向循环链表: 详见今日随堂图片. 自定义代码模拟链表, 思路分析: 1. 自定义SingleNode类, 表示 节点类. 属性: item 数值域(元素域) next 地址域(链接域) 2. 自定义SingleLinkedList类, 表示: 链表 属性: head 表示头结点, 指向第1个节点. 行为: is_empty(self) 链表是否为空 length(self) 链表长度 travel(self. ) 遍历整个链表 add(self, item) 链表头部添加元素 append(self, item) 链表尾部添加元素 insert(self, pos, item) 指定位置添加元素 remove(self, item) 删除节点 search(self, item) 查找节点是否存在 3. 测试. """ # 1. 自定义SingleNode类, 表示 节点类. class SingleNode: # 初始化属性 def __init__(self, item): self.item = item # 元素域(数值域) self.next = None # 链接域(地址域) # 2. 自定义SingleLinkedList类, 表示: 链表 class SingleLinkedList: # 1. 初始化属性. def __init__(self, node=None): self.head = node # 链表的 头结点, 指向第1个节点. # 2. is_empty(self) 链表是否为空 def is_empty(self): # 思路: 判断头结点是否为None, 如果为None, 则链表为空. # 写法1: if else # if self.head is None: # return True # else: # return False # 写法2: 三元表达式 # return True if self.head is None else False # 写法3: 最终版. return self.head is None # return self.head == None # 能用, 但是不推荐 # 3. length(self) 链表长度 def length(self): # 3.1 创建游标(表示当前节点), 默认从头结点开始. cur = self.head # 3.2 定义计数器. count = 0 # 3.3 开始遍历, 只要当前节点不为空, 就一直循环. while cur is not None: # 3.4 计数器 + 1, 然后 cur指向下个节点. count += 1 cur = cur.next # 3.5 循环结束, 列表长度已经获取了, 返回即可. return count # 4. travel(self. ) 遍历整个链表 def travel(self): # 4.1 创建游标(表示当前节点), 默认从头结点开始. cur = self.head # 4.2 只要当前节点不为空, 就一直循环. while cur is not None: # 4.3 打印当前节点的数值域. print(f'数值域: {cur.item}') # 4.4 修改当前节点, 然后 cur指向下个节点. cur = cur.next # 5. add(self, item) 链表头部添加元素 def add(self, item): # 5.1 创建新节点 new_node = SingleNode(item) # 5.2 设置新节点的地址域 指向 头结点 new_node.next = self.head # 5.3 设置头结点指向新节点. self.head = new_node # 6. append(self, item) 链表尾部添加元素 def append(self, item): # 6.1 封装新节点. new_node = SingleNode(item) # 6.2 判断列表如果为空, 直接设置当前节点为头结点即可. if self.is_empty(): self.head = new_node else: # 6.3 走到这里, 说明链表不为空, 需要找到尾结点. # 6.4 创建游标(表示当前节点), 默认从头结点开始. cur = self.head # 6.4 开始遍历, 只要当前节点不为空, 就一直循环. while cur.next is not None: # 6.5 游标后移. cur = cur.next # 6.6 走到这里 cur就是最后1个节点, 设置它的地址域指向新节点即可 cur.next = new_node # 7. insert(self, pos, item) 指定位置添加元素 def insert(self, pos, item): # 7.1 判断索引是否越界, 如果 <= 0 往前加. if pos <= 0: self.add(item) # 7.2 如果索引是 >= 长度的, 就往后加. elif pos >= self.length(): self.append(item) else: # 7.3 走这里, 说明索引合法, 即: 中间的值. 需找到插入位置前的哪个元素. # 7,4 创建游标(表示当前节点), 默认从头结点开始. cur = self.head # 7.5 定义变量, 记录当前节点的位置(可以理解为索引, 但是不是, 因为链表没有索引) count = 0 # 7.6 开始遍历, 只要 当前节点的位置 < pos , 就一直循环. while count < pos - 1: # 7.7 走这里, 说明还没有找到插入前的哪个节点, 就: 节点后移, 计数器+1 cur = cur.next count += 1 # 7.8 走到这里, cur就是插入位置前的那个节点. 先封装内容为新节点. new_node = SingleNode(item) # 7.9 设置 新节点的地址域 指向 插入位置前那个节点的 地址域 new_node.next = cur.next # 7.10 设置 插入位置前的那个节点的地址域 指向 新节点 cur.next = new_node # 8. remove(self, item) 删除节点 def remove(self, item): # 8.1 创建游标(表示当前节点), 默认从头结点开始. cur = self.head # 8.2 定义变量, 记录要删除节点的 前驱节点. pre = None # 8.3 开始遍历, 只要 当前节点不为空, 就一直循环. while cur is not None: # 8.4 判断当前节点是否是要删除的节点. if cur.item == item: # 8.5 判断要删除的节点是否是头结点. if cur == self.head: # 8.6 直接设置头结点为 当前节点的下个节点即可. self.head = cur.next else: # 8.7 走到这里,说明要删除的节点不是头结点. 直接设置 前驱节点的地址域 指向 当前节点的地址域即可. pre.next = cur.next cur.next = None # 删除节点, 断开链接. # 8.8 走这里, 说明删除成功, 直接返回即可, 即: 结束程序. return else: # 8.9 走这里, 说明当前节点不是要删除的节点, 就: 游标后移, 前驱节点后移. pre = cur cur = cur.next # 9. search(self, item) 查找节点是否存在 def search(self, item): # 9.1 创建游标(表示当前节点), 默认从头结点开始. cur = self.head # 9.2 只要当前节点不为空, 就一直循环. while cur is not None: # 9.3 判断当前节点是否是要找的节点, 如果是就返回True if cur.item == item: return True # 9.4 如果当前节点不是要找的节点, 就: 游标后移. cur = cur.next # 9.5 走到这里, 所以节点都找完了, 还没找到, return False return False # 3. 在main中测试 if __name__ == '__main__': # # 3.1 测试节点类. # node1 = SingleNode(10) # # 3.2 打印当前节点的 元素域(数值域) 和 链接域(地址域) # print(f'元素域(数值域): {node1.item}') # 10 # print(f'链接域(地址域): {node1.next}') # None # print(f'node1对象: {node1}') # 地址值, 可以重写 str魔法方法改为打印属性值. # print(f'node1的类型: {type(node1)}') # print('-' * 23) # # # 3.2 测试链表类. # # my_linkedlist = SingleLinkedList() # my_linkedlist = SingleLinkedList(node1) # print(f'头结点为: {my_linkedlist.head}') # print(f'头结点的元素域: {my_linkedlist.head.item}') # 10 # print(f'头结点的地址域: {my_linkedlist.head.next}') # None # 4. 完整测试. # 4.1 创建节点类. node1 = SingleNode('乔峰') # 4.2 将上述的节点作为头结点, 创建链表. my_linkdlist = SingleLinkedList(node1) # my_linkdlist = SingleLinkedList() # 4.3 打印头结点. # print(f'头结点为: {my_linkdlist.head}') # print(f'头结点的数值域为: {my_linkdlist.head.item}') print('-' * 23) # 4.4 测试链表是否为空. print(my_linkdlist.is_empty()) print('-' * 23) # 4.7 测试(往头部)添加元素. my_linkdlist.add('虚竹') my_linkdlist.add('段誉') print('-' * 23) # 4.8 测试(往尾部)添加元素. my_linkdlist.append('王语嫣') my_linkdlist.append('穆婉清') print('-' * 23) # 4.9 测试(指定位置)添加元素. my_linkdlist.insert(-3, '小龙女') my_linkdlist.insert(10, '尹志平') my_linkdlist.insert(2, '阿朱') print('-' * 23) # 4.10 测试删除元素. my_linkdlist.remove('小龙女') my_linkdlist.remove('尹志平') my_linkdlist.remove('乔峰') print('-' * 23) # 4.11 测试查找元素. print(my_linkdlist.search('段誉')) # True print(my_linkdlist.search('乔峰')) # False print('-' * 23) # 4.5 测试链表长度. print(f'链表长度为: {my_linkdlist.length()}') print('-' * 23) # 4.6 测试遍历链表. my_linkdlist.travel() print('-' * 23) ``` ## 冒泡排序_思路及代码 ```python """ 案例: 演示冒泡排序. 冒泡排序介绍: 原理: 相邻元素两两比较, 大的往后走, 这样第一轮比较完毕后, 最大值就在最大索引处. 重复此动作, 直至排序完成. 流程: 假设共 5 个元素 第几轮(索引) 该轮比较的总次数 公式 第1轮(0): 4次 5 - 1 - 0 = 4 第2轮(1): 3次 5 - 1 - 1 = 3 第3轮(2): 2次 5 - 1 - 2 = 2 第4轮(3): 1次 5 - 1 - 3 = 1 要点: 1. 比较的总轮数. 列表长度 - 1 2. 每轮比较的总次数. 列表长度 - 1 - 轮数的索引(从0开始) 3. 谁和谁比较. 索引j 和 j + 1位置的元素比较 时间复杂度: 最优: O(n) 最坏: O(n²) 扩展: 冒泡排序 = 稳定 排序算法 扩展: 外循环的 -1 是什么意思: 减少比较的轮数, 提高效率. 内循环的 -1 是什么意思: 为了防止索引 越界. 内循环的 -i 是什么意思: 减少每轮比较的次数, 提高效率. """ # 1. 定义函数 bubble_sort(my_list), 表示: 冒泡排序. def bubble_sort(my_list): # 形参接收可变类型, 则: 形参的改变直接影响实参. # 1.1 获取列表的长度. n = len(my_list) # 假设 n = 5 # 1.2 外循环, 控制比较的: 轮数 for i in range(n - 1): # i的值: 0, 1, 2, 3 # 细节1: 定义变量, 记录具体的交换次数. count = 0 # 1.3 内循环, 控制比较的: (每轮比较的)总次数 for j in range(n - 1 - i): # 1.4 具体的比较过程, 即: 索引j 和 j + 1位置的元素比较, 大的往后走. if my_list[j] > my_list[j + 1]: # 细节2: 走这里, 说明发生了交换. count += 1 # 1.5 具体的交换过程, a, b = b, a my_list[j], my_list[j + 1] = my_list[j + 1], my_list[j] # 细节3: 打印每轮的交换次数. print(f'第 {i + 1} 轮交换了{count}次') # 细节4: 判断如果本轮没有发生交换, 说明已经是拍完序的, 结束即可. if count == 0: break # 2. 测试 if __name__ == '__main__': # 2.1 定义列表, 记录要排序的元素. # my_list = [5, 3, 6, 7, 2] my_list = [3, 2, 5, 7, 6, 6] # 2.2 调用函数 bubble_sort(my_list), 进行排序. bubble_sort(my_list) # 实参, 可变 # 2.3 打印结果. print(my_list) ``` ## 选择排序_思路及代码 * 思路分析 ![1742611864820](assets/1742611864820.png) ![1742611873582](assets/1742611873582.png) ![1742613266261](assets/1742613266261.png) * 代码实现 ```python """ 案例: 演示选择排序. 选择排序介绍: 原理: 每轮都假设该轮最前边的那个元素为最小值, 然后去 剩下的元素列表中找真正的最小值, 最终交换即可, 本轮就找到了 本轮的最小值, 重复即可. 大白话: 第1轮, 假设 i = 0位置的元素是最小值, 然后用min_index记录住它的索引, 然后去剩下所有元素中找真正的最小值, 找到后就用min_index做记录, 最终判断i 和 min_index是否交换, 第1轮完毕后, 最小值就在最小索引处. 重复该步骤, 直至排序完成. 流程: 假设共 5 个元素 第几轮(索引) 该轮比较的总次数 公式(具体的谁和谁比较) 第1轮(0): 4次 索引0和 1,2,3,4比较 第2轮(1): 3次 索引1和 2,3,4比较 第3轮(2): 2次 索引2和 3,4比较 第4轮(3): 1次 索引3和 4比较 要点: 1. 比较的总轮数. 列表长度 - 1 2. 每轮比较的总次数. i+1 ~ n 3. 谁和谁比较. 索引min_index(初值为i) 和 索引j比较, 索引i 和 索引min_index的值交换 时间复杂度: 最优: O(n²) 最坏: O(n²) 扩展: 选择排序 = 不稳定 排序算法 扩展: 外循环的 -1 是什么意思: 减少比较的轮数, 提高效率. """ # 1. 定义函数select_sort(my_list), 表示: 选择排序. def select_sort(my_list): # 形参接收可变类型, 则: 形参的改变直接影响实参. # 1. 获取列表长度. n = len(my_list) # 2. 外循环, 控制比较的: 轮数. for i in range(n - 1): # 3. 定义变量min_index, 记录住 本轮真正最小值的索引. min_index = i # 4. 内循环, 控制每轮比较的: 次数. for j in range(i + 1, n): # 5. 具体的比较过程 索引min_index(初值为i) 和 索引j比较 if my_list[j] < my_list[min_index]: min_index = j # 记录最小值的索引 # 6. 都到这里, 说明本轮已经找到了最小值, 判断, 并交换. if min_index != i: my_list[min_index], my_list[i] = my_list[i], my_list[min_index] # 2. 测试 if __name__ == '__main__': # 2.1 定义列表, 记录要排序的元素. # my_list = [5, 3, 6, 7, 2] my_list = [2, 3, 5, 6, 7] # 2.2 调用函数select_sort(my_list), 进行排序. select_sort(my_list) # 实参, 可变 # 2.3 打印结果. print(my_list) ``` ## 插入排序_思路及代码 ```python """ 案例: 演示插入排序. 插入排序介绍: 原理: 把列表分成两部分, 假设第1个元素是有序的, 剩下的元素是无序的, 每次都从无序列表中获取1个元素, 和它前边所有元素比较, 决定它的位置, 进行插入. 直至无序列表的元素操作完毕, 剩下的列表就是: 有序的. 流程: 假设共 5 个元素 第几轮(索引) 该轮比较的总次数 公式(具体的谁和谁比较) 第1轮(1): 1次 索引1和 0比较 第2轮(2): 2次 索引2和1, 2和0比较 第3轮(3): 3次 索引3和2, 3和1, 3和0比较 第4轮(4): 4次 索引4和3, 4和2, 4和1, 4和0比较 要点: 1. 比较的总轮数. 列表长度 - 1 range(1, n) 2. 每轮比较的总次数. range(i, 0, -1) 3. 谁和谁比较. 索引j 和 j - 1 位置的元素比较 时间复杂度: 最优: O(n) 最坏: O(n²) 扩展: 插入排序 = 稳定 排序算法 """ # 1. 定义函数 insert_sort(my_list), 表示: 插入排序. def insert_sort(my_list): # 形参接收可变类型, 则: 形参的改变直接影响实参. # 1. 获取列表长度. n = len(my_list) # 假设列表长度为 5 # 2. 外循环, 控制: 比较的轮数. for i in range(1, n): # i的值: 1, 2, 3, 4 # 3. 内循环, 控制: 每轮比较的总次数. for j in range(i, 0, -1): # j的值: 1 2,1 3,2,1 4,3,2,1 # 4. 具体的比较过程, 如果 j < j -1 的元素, 就交换. if my_list[j] < my_list[j - 1]: # j-1的值:0 1,0 2,1,0 3,2,1,0 my_list[j], my_list[j - 1] = my_list[j - 1], my_list[j] else: # 5. 走到这里, 说明元素找到了自己的位置, break即可. break # 2. 测试 if __name__ == '__main__': # 2.1 定义列表, 记录要排序的元素. my_list = [5, 3, 6, 7, 2] # 2.2 调用函数 insert_sort(my_list), 进行排序. insert_sort(my_list) # 实参, 可变 # 2.3 打印结果. print(my_list) ``` ## 二分查找 ```python """ 案例: 演示二分查找, 递归版. 二分查找: 概述: 属于查找类算法, 相对效率比较高, 时间复杂度为: O(log n) 前提: 列表必须是有序的. 原理: 假设列表是 升序 的 1. 比较 要查找的元素 和 列表的中值, 如果一样就返回True, 程序结束. 2. 如果 要查找的元素 比 中值小, 去前半段(中值前) 查找. 3. 如果 要查找的元素 比 中值大, 去后半段(中值后) 查找. 4. 重复上述动作, 直至找完. 如果都找完了, 还找不到, 就返回 False """ # 1. 定义函数 binary_search_recursion(), 表示: 二分查找, 递归版. def binary_search_recursion(my_list, target): """ 该函数是 二分查找的递归版, 实现查找指定元素是否在列表中. :param my_list: 待查找的列表 :param target: 要查找的元素 :return: True:在, False:不在 """ # 1.1 获取列表的长度. n = len(my_list) # 1.2 判断列表是否为空. if n == 0: return False # 1.3 获取列表的 中值(的索引) mid = n // 2 # 1.4 比较 要查找的元素 和 中值. if my_list[mid] == target: return True elif target < my_list[mid]: # 1.5 如果要查找的元素 比 中值小, 去前半段(中值前) 查找, 递归调用. return binary_search_recursion(my_list[:mid], target) else: # 1.6 如果要查找的元素 比 中值大, 去后半段(中值后) 查找, 递归调用. return binary_search_recursion(my_list[mid + 1:], target) # 1.7 走到这里, 说明列表都遍历完了, 还没找到, 返回False return False # 2. 定义函数 binary_search(), 表示: 二分查找, 非递归版. def binary_search(my_list, target): # 1. 定义变量start, end 分别表示列表的开始 和 结束索引. start = 0 end = len(my_list) - 1 # 2. 循环查找, 只要条件满足就一直找. while start <= end: # 3. 计算中间值的 索引. mid = (start + end) // 2 # 4. 比较 要查找的元素 和 中值. if my_list[mid] == target: return True elif target < my_list[mid]: # 5. 如果要查找的元素 比 中值小, 去前半段(中值前) 查找. 即: 修改end的值. end = mid - 1 else: # 6. 如果要查找的元素 比 中值大, 去后半段(中值后) 查找. 即: 修改start的值. start = mid + 1 # 7. 走到这里, 说明列表都遍历完了, 还没找到, 返回False return False # 3. 测试 if __name__ == '__main__': # 2.1 定义列表, 记录: 元素. my_list = [2, 3, 9, 13, 23, 31, 55, 77, 99] # 2.2 查找元素. print(binary_search_recursion(my_list, 23)) # True print(binary_search_recursion(my_list, 25)) # False print('-' * 23) print(binary_search(my_list, 23)) # True print(binary_search(my_list, 25)) # False ``` ## 自定义代码模拟二叉树 * 图解 ![1742638172713](assets/1742638172713.png) * 代码框架 ```python """ 案例: 自定义代码, 模拟二叉树. 树结构解释: 概述: 它属于数据结构的一种, 属于 非线性结构(N个前驱, N个后继) 特点: 1. 有且只能有1个根节点. 2. 每个节点都可以有1个父节点 及 任意个子节点, 根节点除外(没有父节点). 3. 没有子节点的节点, 称之为: 叶子节点. 常用分类: 无序树: 有序树: 二叉树: 完全二叉树: 最后一层不满, 其它都是满的. 满二叉树: 都是满的. 非完全二叉树: 中间有断的. 平衡二叉树: 任意节点的两个子树的高度差不超过1 我们用的最多的就是: 二叉树 存储: 顺序存储: 既要存储数据, 又要存储节点的关系. 链式存储: 采用节点(item, lchild, rchild)的方式, 形成链表来存储 抽取方法的快捷键: ctrl + alt + M """ # 1. 定义Node类, 表示二叉树的节点. class Node: # 初始化属性 def __init__(self, item): self.item = item # 元素域, 即: 节点存储的数据. self.lchild = None # 左子节点 self.rchild = None # 右子节点 # 2. 自定义BinaryTree类, 表示二叉树 class BinaryTree: # 2.1 初始化属性. def __init__(self, node=None): self.root = node # 根节点, 类似于: 链表的 self.head 头结点 # 2.2 定义add函数, 表示: 添加节点 def add(self, item): pass # 2.3 定义breadth()函数, 表示: 广度优先遍历(逐层遍历, 一层一层遍历) def breadth(self): pass # 2.4 定义preorder()函数, 表示: 深度优先之先序遍历(根左右) def preorder(self): pass # 2.5 定义inorder()函数, 表示: 深度优先之中序遍历(左根右) def inorder(self): pass # 2.6 定义postorder()函数, 表示: 深度优先之后序遍历(左右根) def postorder(self): pass # 3. 编写测试函数, 用于测试对应的功能. # 3.1 定义函数 dm01_测试节点和二叉树() def dm01_测试节点和二叉树(): # 1. 创建节点 node1 = Node('A') # 2. 打印节点的 元素域, 左子树, 右子树. print(node1.item) # A print(node1.lchild) # None print(node1.rchild) # None print('-' * 23) # 3. 测试二叉树. # bt = BinaryTree() # 空的 # print(bt.root) # None bt = BinaryTree(node1) print(bt.root) # 根节点(的地址) print(bt.root.item) # 根节点的元素域 -> A # 4.在main函数中具体测试 if __name__ == '__main__': dm01_测试节点和二叉树() ``` * 完整代码 ```python """ 案例: 自定义代码, 模拟二叉树. 树结构解释: 概述: 它属于数据结构的一种, 属于 非线性结构(N个前驱, N个后继) 特点: 1. 有且只能有1个根节点. 2. 每个节点都可以有1个父节点 及 任意个子节点, 根节点除外(没有父节点). 3. 没有子节点的节点, 称之为: 叶子节点. 常用分类: 无序树: 有序树: 二叉树: 完全二叉树: 最后一层不满, 其它都是满的. 满二叉树: 都是满的. 非完全二叉树: 中间有断的. 平衡二叉树: 任意节点的两个子树的高度差不超过1 我们用的最多的就是: 二叉树 存储: 顺序存储: 既要存储数据, 又要存储节点的关系. 链式存储: 采用节点(item, lchild, rchild)的方式, 形成链表来存储 抽取方法的快捷键: ctrl + alt + M """ # 1. 定义Node类, 表示二叉树的节点. class Node: # 初始化属性 def __init__(self, item): self.item = item # 元素域, 即: 节点存储的数据. self.lchild = None # 左子节点 self.rchild = None # 右子节点 # 2. 自定义BinaryTree类, 表示二叉树 class BinaryTree: # 2.1 初始化属性. def __init__(self, node=None): self.root = node # 根节点, 类似于: 链表的 self.head 头结点 # 2.2 定义add函数, 表示: 添加节点 def add(self, item): # 1. 把item封装成节点 new_node = Node(item) # 2. 判断根节点是否为空, 如果为空, 设置当前节点为根节点. if self.root is None: self.root = new_node return # 核心 # 3. 创建队列, 添加 根节点到队列中. queue = [] queue.append(self.root) # 4. 通过 while True死循环, 找到空缺的节点位置. while True: # 5. 获取队列的第1个元素. node = queue.pop(0) # 6. 判断当前节点的左子树是否为空. if node.lchild is None: # 6.1 把新节点设置为当前节点的左子树, 并结束. node.lchild = new_node return else: # 6.2 走这里, 说明左子树不为空, 把当前节点的左子树, 添加到队列中. queue.append(node.lchild) # 7. 判断当前节点的右子树是否为空. if node.rchild is None: # 7.1 把新节点设置为当前节点的右子树, 并结束. node.rchild = new_node return else: # 7.2 走这里, 说明右子树不为空, 把当前节点的右子树, 添加到队列中. queue.append(node.rchild) # 2.3 定义breadth_travel()函数, 表示: 广度优先遍历(逐层遍历, 一层一层遍历) def breadth_travel(self): # 1. 判断根节点是否为空. if self.root is None: return # 2. 创建队列, 添加 根节点到队列中. queue = [] queue.append(self.root) # 3. 循环打印内容, 只要队列不为空, 就一直遍历. while len(queue) != 0: # 4. 获取队列的第1个元素. node = queue.pop(0) # 5. 打印该节点的 元素域. print(node.item, end=' ') # 6.判断当前节点的左子树是否存在, 存在就添加到队列中. if node.lchild is not None: queue.append(node.lchild) # 7. 判断当前节点的右子树是否存在, 存在就添加到队列中. if node.rchild is not None: queue.append(node.rchild) # 2.4 定义preorder_travel()函数, 表示: 深度优先之先序遍历(根左右) def preorder_travel(self, root): # 1.判断根节点是否不为空, 不为空就打印. if root is not None: # 2. 打印根节点的 元素域 print(root.item, end=' ') # 3. 递归遍历左子树. self.preorder_travel(root.lchild) # 4. 递归遍历右子树. self.preorder_travel(root.rchild) # 2.5 定义inorder()函数, 表示: 深度优先之中序遍历(左根右) def inorder_travel(self, root): # 1.判断根节点是否不为空, 不为空就打印. if root is not None: # 2. 递归遍历左子树. self.inorder_travel(root.lchild) # 3. 打印根节点的 元素域 print(root.item, end=' ') # 4. 递归遍历右子树. self.inorder_travel(root.rchild) # 2.6 定义postorder()函数, 表示: 深度优先之后序遍历(左右根) def postorder_travel(self, root): # 1.判断根节点是否不为空, 不为空就打印. if root is not None: # 2. 递归遍历左子树. self.postorder_travel(root.lchild) # 3. 递归遍历右子树. self.postorder_travel(root.rchild) # 4. 打印根节点的 元素域 print(root.item, end=' ') # 3. 编写测试函数, 用于测试对应的功能. # 3.1 定义函数 dm01_测试节点和二叉树() def dm01_测试节点和二叉树(): # 1. 创建节点 node1 = Node('A') # 2. 打印节点的 元素域, 左子树, 右子树. print(node1.item) # A print(node1.lchild) # None print(node1.rchild) # None print('-' * 23) # 3. 测试二叉树. # bt = BinaryTree() # 空的 # print(bt.root) # None bt = BinaryTree(node1) print(bt.root) # 根节点(的地址) print(bt.root.item) # 根节点的元素域 -> A # 3.2 定义函数 dm02_模拟队列取元素() def dm02_模拟队列取元素(): # 1. 创建队列, 特点: 先进先出 queue = [] # 2. 模拟往队列中添加元素. queue.append('A') queue.append('B') queue.append('C') # 3. 模拟从队列中取出元素. print(queue.pop(0)) # A 删除索引为0的元素, 并返回该元素, 即: 模拟从 队列中获取 元素. print(queue.pop(0)) # B print(queue.pop(0)) # C # 4.打印队列 print(queue) # ['A', 'B', 'C'] # 3.3 定义函数 dm03_广度优先遍历() def dm03_广度优先遍历(): # 1. 创建二叉树对象. bt = BinaryTree() # 2. 添加元素. bt.add('A') bt.add('B') bt.add('C') bt.add('D') bt.add('E') bt.add('F') bt.add('G') bt.add('H') bt.add('I') bt.add('J') # 3. 广度优先遍历. bt.breadth_travel() # 3.4 定义函数 dm04_深度优先遍历() def dm04_深度优先遍历(): # 1.创建二叉树对象. bt = BinaryTree() # 2. 添加元素. bt.add(0) bt.add(1) bt.add(2) bt.add(3) bt.add(4) bt.add(5) bt.add(6) bt.add(7) bt.add(8) bt.add(9) # 3. 深度优先遍历. print('先序(根左右): ', end=' ') bt.preorder_travel(bt.root) print('\n中序(左根右): ', end=' ') bt.inorder_travel(bt.root) print('\n后序(左右根): ', end=' ') bt.postorder_travel(bt.root) # 4.在main函数中具体测试 if __name__ == '__main__': # dm01_测试节点和二叉树() # dm02_模拟队列取元素() # dm03_广度优先遍历() dm04_深度优先遍历() ```