Python 学习笔记(第八期)——面向对象编程:类、继承与多态
Python 学习笔记(第八期)——面向对象编程:类、继承与多态
更多Python知识:Python基础入门
我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~
📖 引言:从“执行者”到“组织者”
在前七期中,我们一直采用面向过程的编程方式——把程序拆解为一系列步骤,用函数封装每个步骤,然后按顺序调用。这种方式适合解决相对简单的问题,就像照着菜谱一步步做菜——步骤清晰,但灵活性有限。
但随着程序规模的增长,面向过程的局限性逐渐暴露。假设你要开发一个“校园管理系统”,涉及学生、教师、课程、班级等多个实体,每个实体都有自己的属性和行为。如果还是用函数加数据结构的方式,代码会变得散乱,维护起来非常困难。
这时候,面向对象编程(OOP) 就派上了用场。OOP 的核心思想是:把程序中的事物抽象为“对象”,每个对象既有数据(属性),又有行为(方法)。对象之间通过相互协作来完成程序的功能。
💡 白话理解:面向过程编程像是“导演写剧本”——一切按步骤执行;面向对象编程像是“制片人搭建剧组”——每个角色(对象)都有自己的职责,角色之间通过沟通协作完成整部戏。
📑 本期目录
- 面向对象概述(面向过程 vs 面向对象)
- 类的定义与使用(属性与方法)
- 类的成员(类属性、实例属性、方法)
- 特殊方法(构造方法与析构方法)
- 封装(私有成员与公有接口)
- 继承(单继承、多继承、方法重写)
- 多态(同一接口,不同行为)
- 运算符重载(让自定义对象支持运算符)
- 综合实战演练(两个完整案例)
- 总结与知识点一览表
正文
一、面向对象概述
1.1 面向过程 vs 面向对象
在开始学习 OOP 之前,我们先通过一个日常例子理解两种编程范式的区别。
场景:制作一杯美式咖啡。
面向过程方式——关注“步骤”:
- 准备材料:水、咖啡粉
- 将水加热至沸腾
- 在滤杯中放置滤纸
- 用热水冲泡咖啡粉
- 等待过滤完成
- 得到一杯咖啡
代码结构:函数1() → 函数2() → 函数3() → ... → 完成
面向对象方式——关注“角色”:
- 咖啡机:有“煮制”功能,能接收水和咖啡粉
- 水:有“温度”属性,可以被加热
- 咖啡粉:有“研磨程度”属性
- 杯子:有“容量”属性,用于盛装咖啡
代码结构:咖啡机.煮制(水, 咖啡粉) → 得到咖啡
💡 核心区别:
- 面向过程:我要做什么步骤?(以动作为中心)
- 面向对象:谁来做这件事?(以事物为中心)
面向对象的优势:
| 优势 | 说明 | 示例 |
|---|---|---|
| 可维护性 | 修改某个对象时,不影响其他对象 | 想换咖啡机品牌?只需替换“咖啡机”对象 |
| 代码复用 | 通过继承,子类自动拥有父类功能 | 想喝茶?直接继承“饮品制作”类,替换原料即可 |
| 扩展性 | 新增功能时,只需新增类或方法 | 想增加“拿铁”品类?新增类即可 |
1.2 类和对象
面向对象编程有两个核心概念:
| 概念 | 定义 | 类比 |
|---|---|---|
| 类(Class) | 一类事物的抽象描述,定义共同的特征和行为 | 像“蓝图”或“模板” |
| 对象(Object) | 类的具体实例,真实存在的个体 | 像按蓝图盖出来的“房子” |
类:人类(Person)—— 共性:有姓名、年龄,能说话、能走路
↓ 实例化
对象:张三 —— 具体的人,姓名=张三,年龄=18
对象:李四 —— 具体的人,姓名=李四,年龄=20
💡 白话理解:类就像是“饼干模具”,对象是用模具做出来的“饼干”。同一套模具可以做出一批形状相同的饼干,每块饼干都是一个独立的对象。
二、类的定义与使用
2.1 类的定义
类由三部分组成:
| 组成部分 | 说明 | 示例 |
|---|---|---|
| 类名 | 标识类,使用大驼峰命名法 | Person、StudentInfo |
| 属性 | 描述事物的静态特征 | 姓名、年龄、身高 |
| 方法 | 描述事物的动态行为 | 说话、走路、学习 |
# 定义类
class Car:
"""汽车类"""
# 类属性(所有实例共享)
wheels = 4
# 方法(行为)
def drive(self):
print("汽车正在行驶")
def stop(self):
print("汽车已停止")
2.2 对象的创建与使用
# 创建对象(实例化)
car1 = Car()
car2 = Car()
# 访问属性
print(car1.wheels) # 4
# 调用方法
car1.drive() # 汽车正在行驶
car1.stop() # 汽车已停止
类的结构图:
┌─────────────────────────┐
│ Car │ ← 类名
├─────────────────────────┤
│ wheels = 4 │ ← 类属性(静态特征)
├─────────────────────────┤
│ def drive(self): │ ← 方法(动态行为)
│ print("行驶") │
│ def stop(self): │
│ print("停止") │
└─────────────────────────┘
↓ 实例化
┌─────────────┐ ┌─────────────┐
│ car1 │ │ car2 │ ← 对象(具体实例)
│ wheels=4 │ │ wheels=4 │
│ 能行驶 │ │ 能行驶 │
│ 能停止 │ │ 能停止 │
└─────────────┘ └─────────────┘
三、类的成员
类的成员分为属性和方法两大类。
3.1 属性(类属性 vs 实例属性)
| 类型 | 定义位置 | 访问方式 | 修改影响 |
|---|---|---|---|
| 类属性 | 类内部、方法外部 | 通过类或对象访问 | 通过类修改影响所有实例 |
| 实例属性 | 方法内部(用 self) |
只能通过对象访问 | 每个实例独立,互不影响 |
class Car:
# 类属性(所有实例共享)
wheels = 4
def drive(self):
# 实例属性(每个实例独立)
self.speed = 0
self.color = "白色"
# 1. 访问类属性
print(Car.wheels) # 4(通过类访问)
print(car1.wheels) # 4(通过对象访问)
# 2. 通过类修改类属性 → 影响所有实例
Car.wheels = 3
print(car1.wheels) # 3
print(car2.wheels) # 3
# 3. 通过对象修改类属性 → 只影响该实例
car1.wheels = 4
print(car1.wheels) # 4
print(car2.wheels) # 3(不受影响)
实例属性的动态添加:
class Car:
def drive(self):
self.speed = 0
car = Car()
car.drive()
print(car.speed) # 0
# 动态添加新实例属性
car.color = "红色"
print(car.color) # 红色
3.2 方法(实例方法、类方法、静态方法)
| 类型 | 装饰器 | 第一个参数 | 访问方式 | 适用场景 |
|---|---|---|---|---|
| 实例方法 | 无 | self(对象本身) |
只能通过对象调用 | 需要访问或修改实例属性 |
| 类方法 | @classmethod |
cls(类本身) |
通过类或对象调用 | 需要访问或修改类属性 |
| 静态方法 | @staticmethod |
无 | 通过类或对象调用 | 与类相关但不需要访问类或实例 |
实例方法:
class Car:
def drive(self): # self 代表对象本身
print("正在行驶")
print(f"当前对象:{self}")
car = Car()
car.drive() # 通过对象调用
# Car.drive() # ❌ 通过类调用会报错
类方法:
class Car:
wheels = 4
@classmethod
def show_wheels(cls): # cls 代表类本身
print(f"车轮数:{cls.wheels}")
cls.wheels = 3 # 可以修改类属性
# 通过类或对象都可以调用
Car.show_wheels() # 车轮数:4
car = Car()
car.show_wheels() # 车轮数:4
静态方法:
class Car:
@staticmethod
def test(): # 无特殊参数
print("这是静态方法,不访问类或实例的属性和方法")
Car.test() # 通过类调用
car = Car()
car.test() # 通过对象调用
三种方法对比:
| 对比维度 | 实例方法 | 类方法 | 静态方法 |
|---|---|---|---|
| 能否访问实例属性 | ✅ 可以 | ❌ 不可以 | ❌ 不可以 |
| 能否访问类属性 | ✅ 可以(通过 self.__class__) |
✅ 可以(通过 cls) |
❌ 不可以(除非用 类名.属性) |
| 能否修改类属性 | ✅ 可以(通过 self.__class__) |
✅ 可以(通过 cls) |
❌ 不可以 |
| 适用场景 | 操作实例数据的业务逻辑 | 工厂方法、修改类属性 | 工具函数,与类相关但无需类数据 |
四、特殊方法
4.1 构造方法 __init__() ——“初始化器”
构造方法在创建对象时自动调用,用于初始化对象的属性。每个类默认都有一个无参构造方法,我们可以显式定义它。
无参构造方法:所有对象属性有相同的初始值。
class Car:
def __init__(self): # 无参构造方法
self.color = "白色"
self.speed = 0
def drive(self):
print(f"颜色:{self.color},速度:{self.speed}")
car1 = Car() # 自动调用 __init__()
car1.drive() # 颜色:白色,速度:0
有参构造方法:每个对象可以有不同的初始值。
class Car:
def __init__(self, color, speed=0): # 有参构造方法
self.color = color
self.speed = speed
def drive(self):
print(f"颜色:{self.color},速度:{self.speed}")
car1 = Car("红色") # 颜色:红色,速度:0
car2 = Car("蓝色", 100) # 颜色:蓝色,速度:100
car1.drive() # 颜色:红色,速度:0
car2.drive() # 颜色:蓝色,速度:100
💡 白话理解:
__init__()就像“出厂设置”——新车出厂时,需要给它设置颜色、里程等初始状态。不同车有不同的配置,通过构造方法的参数来定制。
4.2 析构方法 __del__() ——“销毁器”
析构方法在对象被销毁时自动调用,用于释放资源(如关闭文件、断开网络连接)。
class Car:
def __init__(self, color):
self.color = color
print(f"{color} 汽车已创建")
def __del__(self):
print(f"{self.color} 汽车已销毁")
# 创建对象
car = Car("红色") # 红色 汽车已创建
# 手动销毁对象
del car # 红色 汽车已销毁
对象销毁的时机:
- 程序结束时,所有对象被自动销毁
- 使用
del语句手动销毁 - 对象的引用计数器变为 0 时自动销毁
引用计数器机制:
car1 = Car("红色") # 引用计数:1
car2 = car1 # 引用计数:2
car3 = car1 # 引用计数:3
del car1 # 引用计数:2
del car2 # 引用计数:1
del car3 # 引用计数:0 → 对象被销毁
五、封装——把数据“藏起来”
封装是面向对象的三大特性之一(封装、继承、多态),其核心思想是:
对外隐藏类的内部细节,提供公开的访问接口。
5.1 私有成员
在属性或方法名前加双下划线 __,将其变为私有成员。
| 成员类型 | 命名方式 | 访问范围 | 示例 |
|---|---|---|---|
| 公有成员 | 常规命名 | 任何地方都可以访问 | self.name |
| 私有成员 | 以 __ 开头 |
只能在类内部访问 | self.__age |
class Person:
def __init__(self, name, age):
self.name = name # 公有属性
self.__age = age # 私有属性
def get_age(self): # 公有方法(访问接口)
return self.__age
def set_age(self, new_age): # 公有方法(修改接口)
if 0 < new_age <= 120: # 带合法性检查
self.__age = new_age
else:
print("年龄无效!")
def __secret(self): # 私有方法
print("这是私有方法")
person = Person("张三", 18)
# 公有属性:可以直接访问
print(person.name) # 张三
# 私有属性:不能直接访问
# print(person.__age) # ❌ AttributeError
# 通过公有接口访问
print(person.get_age()) # 18
# 通过公有接口修改(带校验)
person.set_age(25)
print(person.get_age()) # 25
person.set_age(200) # 年龄无效!
💡 白话理解:封装就像银行的“柜台服务”——客户不能直接进入金库(私有属性),而是通过柜台窗口(公有方法)办理业务。这样做的好处是:银行可以在柜台窗口做安全检查(数据校验),确保每一笔操作都合规。
为什么要封装?
| 原因 | 说明 |
|---|---|
| 数据安全 | 防止外部直接修改关键数据,造成数据不一致 |
| 数据校验 | 在设置数据时进行合法性检查(如年龄不能为负) |
| 隐藏实现细节 | 外部只需知道“怎么用”,不需要知道“怎么实现” |
| 降低耦合 | 修改内部实现时,不影响外部调用代码 |
六、继承——让代码“遗传”下去
继承是面向对象的第二大特性,它允许一个类继承另一个类的属性和方法,从而实现代码复用。
6.1 单继承
子类继承一个父类,自动拥有父类的公有成员(私有成员不会继承)。
# 父类(基类)
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} 正在吃东西")
def sleep(self):
print(f"{self.name} 正在睡觉")
# 子类(派生类)
class Dog(Animal): # 继承 Animal
def bark(self): # 添加新方法
print(f"{self.name} 汪汪叫")
# 子类(派生类)
class Cat(Animal): # 继承 Animal
def meow(self): # 添加新方法
print(f"{self.name} 喵喵叫")
# 使用
dog = Dog("旺财")
dog.eat() # 旺财 正在吃东西(继承自父类)
dog.sleep() # 旺财 正在睡觉(继承自父类)
dog.bark() # 旺财 汪汪叫(子类特有)
cat = Cat("咪咪")
cat.eat() # 咪咪 正在吃东西(继承自父类)
cat.meow() # 咪咪 喵喵叫(子类特有)
继承关系图:
┌─────────────┐
│ Animal │ ← 父类
│ - name │
│ + eat() │
│ + sleep() │
└──────┬──────┘
│ 继承
┌───────┴───────┐
↓ ↓
┌─────────────┐ ┌─────────────┐
│ Dog │ │ Cat │ ← 子类
│ + bark() │ │ + meow() │
└─────────────┘ └─────────────┘
6.2 多继承
一个子类可以继承多个父类,拥有所有父类的公有成员。
class Flyable:
def fly(self):
print("正在飞行")
class Swimmable:
def swim(self):
print("正在游泳")
# 多继承:同时继承 Flyable 和 Swimmable
class Duck(Flyable, Swimmable):
def quack(self):
print("嘎嘎叫")
duck = Duck()
duck.fly() # 正在飞行(继承自 Flyable)
duck.swim() # 正在游泳(继承自 Swimmable)
duck.quack() # 嘎嘎叫(子类特有)
多继承的方法查找顺序(MRO):
当多个父类有同名方法时,Python 按照方法解析顺序(MRO) 从左到右查找。
class A:
def hello(self):
print("A.hello")
class B:
def hello(self):
print("B.hello")
class C(A, B): # 先继承 A,再继承 B
pass
c = C()
c.hello() # A.hello(先找 A)
# 查看 MRO 顺序
print(C.__mro__) # (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
6.3 方法重写与 super()
子类可以重写父类的方法,实现自己的逻辑。如果需要调用父类的方法,可以使用 super()。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print("动物发出声音")
class Dog(Animal):
def speak(self): # 重写父类方法
super().speak() # 调用父类方法
print("汪汪叫") # 子类新增逻辑
dog = Dog("旺财")
dog.speak()
运行结果:
动物发出声音
汪汪叫
super() 的主要作用:
| 场景 | 说明 |
|---|---|
| 调用父类构造方法 | 在子类 __init__() 中调用 super().__init__() 初始化父类属性 |
| 扩展父类方法 | 在子类方法中先调用父类方法,再添加额外逻辑 |
| 多继承中调用特定父类方法 | 按 MRO 顺序调用父类方法 |
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, grade):
super().__init__(name, age) # 调用父类构造方法
self.grade = grade # 子类新增属性
def info(self):
print(f"姓名:{self.name},年龄:{self.age},年级:{self.grade}")
stu = Student("张三", 18, "高三")
stu.info() # 姓名:张三,年龄:18,年级:高三
七、多态——同一种行为,不同的表现
多态是面向对象的第三大特性,它的核心含义是:
不同类的对象对同一方法调用,表现出不同的行为。
多态的前提条件:
- 存在继承关系
- 子类重写父类的方法
- 使用父类类型的引用调用被子类重写的方法
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("汪汪叫")
class Cat(Animal):
def speak(self):
print("喵喵叫")
class Duck(Animal):
def speak(self):
print("嘎嘎叫")
# 多态:同一个函数,传入不同的对象,表现出不同的行为
def animal_speak(animal: Animal):
animal.speak()
# 测试
dog = Dog()
cat = Cat()
duck = Duck()
animal_speak(dog) # 汪汪叫
animal_speak(cat) # 喵喵叫
animal_speak(duck) # 嘎嘎叫
运行结果:
汪汪叫
喵喵叫
嘎嘎叫
💡 白话理解:多态就像“开会发言”——同样是在会议上“说话”,老板说的是战略,技术说的是架构,销售说的是业绩。同一句话“说话”,不同角色有不同的表现。
八、运算符重载
运算符重载允许我们为自定义类定义运算符的行为,使对象支持 +、-、*、in 等运算。
| 特殊方法 | 对应运算符 | 说明 |
|---|---|---|
__add__(self, other) |
+ |
加法 |
__sub__(self, other) |
- |
减法 |
__mul__(self, other) |
* |
乘法 |
__truediv__(self, other) |
/ |
除法 |
__mod__(self, other) |
% |
取余 |
__pow__(self, other) |
** |
幂运算 |
__eq__(self, other) |
== |
相等判断 |
__lt__(self, other) |
< |
小于判断 |
__contains__(self, item) |
in |
成员判断 |
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 __eq__(self, other):
"""重载 == 运算符:向量相等判断"""
return self.x == other.x and self.y == other.y
def __str__(self):
"""重载 str() 输出"""
return f"Vector({self.x}, {self.y})"
# 使用
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # 调用 __add__
print(v3) # Vector(4, 6)
v4 = v2 - v1 # 调用 __sub__
print(v4) # Vector(2, 2)
v5 = v1 * 3 # 调用 __mul__
print(v5) # Vector(3, 6)
print(v1 == v2) # False
运行结果:
Vector(4, 6)
Vector(2, 2)
Vector(3, 6)
False
💡 实战意义:运算符重载让自定义类的对象能够像内置类型一样使用运算符,代码更加直观。比如向量、矩阵、复数的数学运算,用
+比用add()方法更自然。
九、综合实战演练
实战一:好友管理系统
需求描述:实现一个基于面向对象思想的好友管理系统,支持以下功能:
- 添加好友:输入姓名和备注,添加到好友列表
- 删除好友:输入姓名,从列表中删除
- 备注好友:修改好友的备注名
- 展示好友:列出所有好友及其备注
- 好友分组:按分组显示好友
涉及知识点:类的定义、属性、方法、封装、字典操作
class Friend:
"""好友类"""
def __init__(self, name, nickname="", group="默认"):
self.name = name
self.nickname = nickname if nickname else name
self.group = group
def update_nickname(self, new_nickname):
"""修改备注"""
self.nickname = new_nickname
def update_group(self, new_group):
"""修改分组"""
self.group = new_group
def __str__(self):
return f"{self.nickname}({self.name})"
class FriendManager:
"""好友管理类"""
def __init__(self):
self.friends = [] # 好友列表
self.groups = {} # 分组映射:分组名 → 好友列表
def add_friend(self, name, nickname="", group="默认"):
"""添加好友"""
# 检查是否已存在
for f in self.friends:
if f.name == name:
print(f"❌ 好友 {name} 已存在!")
return False
friend = Friend(name, nickname, group)
self.friends.append(friend)
# 添加到分组
if group not in self.groups:
self.groups[group] = []
self.groups[group].append(friend)
print(f"✅ 已添加好友:{friend}")
return True
def delete_friend(self, name):
"""删除好友"""
for friend in self.friends:
if friend.name == name:
self.friends.remove(friend)
# 从分组中删除
for group, members in self.groups.items():
if friend in members:
members.remove(friend)
print(f"✅ 已删除好友:{name}")
return True
print(f"❌ 未找到好友:{name}")
return False
def update_nickname(self, name, new_nickname):
"""修改备注"""
for friend in self.friends:
if friend.name == name:
friend.update_nickname(new_nickname)
print(f"✅ 已修改备注:{name} → {new_nickname}")
return True
print(f"❌ 未找到好友:{name}")
return False
def update_group(self, name, new_group):
"""修改分组"""
for friend in self.friends:
if friend.name == name:
# 从旧分组移除
for group, members in self.groups.items():
if friend in members:
members.remove(friend)
# 添加到新分组
if new_group not in self.groups:
self.groups[new_group] = []
self.groups[new_group].append(friend)
friend.update_group(new_group)
print(f"✅ 已修改分组:{name} → {new_group}")
return True
print(f"❌ 未找到好友:{name}")
return False
def show_all(self):
"""展示所有好友"""
if not self.friends:
print("📭 暂无好友")
return
print("\n" + "=" * 40)
print(" 📋 所有好友")
print("=" * 40)
for friend in self.friends:
print(f" {friend}(分组:{friend.group})")
print("=" * 40)
def show_by_group(self):
"""按分组展示好友"""
if not self.groups:
print("📭 暂无分组")
return
print("\n" + "=" * 40)
print(" 📂 按分组展示")
print("=" * 40)
for group, members in self.groups.items():
print(f"\n【{group}】")
if not members:
print(" 空")
else:
for friend in members:
print(f" - {friend}")
print("\n" + "=" * 40)
def menu(self):
"""显示菜单"""
menu = """
==========================
📱 好友管理系统
==========================
1. 添加好友
2. 删除好友
3. 修改备注
4. 修改分组
5. 查看所有好友
6. 按分组查看
0. 退出
==========================
"""
print(menu)
def run(self):
"""运行主程序"""
while True:
self.menu()
choice = input("请选择功能(0-6):")
if choice == "0":
print("👋 再见!")
break
elif choice == "1":
name = input("请输入好友姓名:").strip()
nickname = input("请输入备注(可直接回车跳过):").strip()
group = input("请输入分组(默认:默认):").strip() or "默认"
self.add_friend(name, nickname, group)
elif choice == "2":
name = input("请输入要删除的姓名:").strip()
self.delete_friend(name)
elif choice == "3":
name = input("请输入好友姓名:").strip()
new_nickname = input("请输入新备注:").strip()
self.update_nickname(name, new_nickname)
elif choice == "4":
name = input("请输入好友姓名:").strip()
new_group = input("请输入新分组:").strip()
self.update_group(name, new_group)
elif choice == "5":
self.show_all()
elif choice == "6":
self.show_by_group()
else:
print("❌ 无效选项,请重新选择!")
# 运行
if __name__ == "__main__":
manager = FriendManager()
# 添加示例数据
manager.add_friend("刘备", "刘皇叔", "三国")
manager.add_friend("关羽", "关云长", "三国")
manager.run()
实战二:人机猜拳游戏
需求描述:实现一个基于面向对象的人机猜拳游戏,支持以下功能:
- 玩家可以选择石头、剪刀、布
- 电脑随机出拳
- 判断胜负并记录得分
- 支持多轮游戏
涉及知识点:类的定义、继承、多态、封装、随机数
import random
class Player:
"""玩家基类"""
def __init__(self, name):
self.name = name
self.score = 0
self.gesture = None
def choose_gesture(self):
"""选择手势(子类重写)"""
pass
def win(self):
self.score += 1
def __str__(self):
return f"{self.name}:{self.score}分"
class HumanPlayer(Player):
"""人类玩家"""
GESTURE_MAP = {"1": "石头", "2": "剪刀", "3": "布"}
def choose_gesture(self):
print("\n请选择出拳:")
print("1:石头 ✊")
print("2:剪刀 ✌️")
print("3:布 🖐️")
choice = input("请输入编号(1-3):")
self.gesture = self.GESTURE_MAP.get(choice)
if not self.gesture:
print("❌ 无效输入,请重新选择!")
return self.choose_gesture()
print(f"你出了:{self.gesture}")
return self.gesture
class ComputerPlayer(Player):
"""电脑玩家"""
GESTURES = ["石头", "剪刀", "布"]
def choose_gesture(self):
self.gesture = random.choice(self.GESTURES)
print(f"电脑出了:{self.gesture}")
return self.gesture
class Game:
"""猜拳游戏类"""
def __init__(self, rounds=3):
self.human = HumanPlayer("玩家")
self.computer = ComputerPlayer("电脑")
self.rounds = rounds
self.win_rules = {
("石头", "剪刀"): "玩家",
("剪刀", "布"): "玩家",
("布", "石头"): "玩家",
("剪刀", "石头"): "电脑",
("布", "剪刀"): "电脑",
("石头", "布"): "电脑",
}
def judge(self, human_gesture, computer_gesture):
"""判断胜负"""
if human_gesture == computer_gesture:
return "平局"
result = self.win_rules.get((human_gesture, computer_gesture))
if result == "玩家":
self.human.win()
return "你赢了!🎉"
else:
self.computer.win()
return "电脑赢了!😢"
def play_round(self, round_num):
"""进行一轮游戏"""
print(f"\n{'=' * 40}")
print(f" 第 {round_num} 轮")
print('=' * 40)
# 双方出拳
human_gesture = self.human.choose_gesture()
computer_gesture = self.computer.choose_gesture()
# 判断胜负
result = self.judge(human_gesture, computer_gesture)
print(f"结果:{result}")
def show_score(self):
"""显示得分"""
print("\n" + "=" * 40)
print(" 📊 当前比分")
print("=" * 40)
print(f"玩家:{self.human.score} 分")
print(f"电脑:{self.computer.score} 分")
if self.human.score > self.computer.score:
print("🏆 玩家领先!")
elif self.human.score < self.computer.score:
print("💻 电脑领先!")
else:
print("🤝 平局!")
print("=" * 40)
def start(self):
"""开始游戏"""
print("=" * 40)
print(" ✊ 人机猜拳游戏 ✊")
print("=" * 40)
print(f"一共进行 {self.rounds} 轮")
for i in range(1, self.rounds + 1):
self.play_round(i)
self.show_score()
# 最终结果
print("\n" + "=" * 40)
print(" 🏁 游戏结束")
print("=" * 40)
if self.human.score > self.computer.score:
print("🎉 恭喜你获得最终胜利!")
elif self.human.score < self.computer.score:
print("😢 很遗憾,电脑获胜!")
else:
print("🤝 最终平局!")
print("=" * 40)
# 运行
if __name__ == "__main__":
game = Game(rounds=3)
game.start()
运行示例:
========================================
✊ 人机猜拳游戏 ✊
========================================
一共进行 3 轮
========================================
第 1 轮
========================================
请选择出拳:
1:石头 ✊
2:剪刀 ✌️
3:布 🖐️
请输入编号(1-3):1
你出了:石头
电脑出了:布
结果:电脑赢了!😢
========================================
📊 当前比分
========================================
玩家:0 分
电脑:1 分
💻 电脑领先!
========================================
...(后续轮次)
📝 总结与知识点一览表
核心知识点汇总
| 类别 | 知识点 | 关键语法 |
|---|---|---|
| 类定义 | 使用 class 定义类 |
class ClassName: |
| 对象创建 | 实例化类 | obj = ClassName() |
| 类属性 | 所有实例共享 | class_var = value |
| 实例属性 | 每个实例独立 | self.var = value |
| 实例方法 | 操作实例数据 | def method(self): |
| 类方法 | 操作类数据 | @classmethod |
| 静态方法 | 工具函数 | @staticmethod |
| 构造方法 | 初始化对象 | __init__(self, ...) |
| 析构方法 | 销毁对象 | __del__(self) |
| 私有成员 | 双下划线开头 | __private_var |
| 封装 | 私有属性 + 公有方法 | 隐藏内部实现 |
| 单继承 | 继承一个父类 | class Sub(Parent): |
| 多继承 | 继承多个父类 | class Sub(P1, P2): |
| 方法重写 | 覆盖父类方法 | 同名方法重新实现 |
| super() | 调用父类方法 | super().method() |
| 多态 | 同一接口不同行为 | 父类引用调用子类方法 |
| 运算符重载 | 自定义运算符行为 | __add__、__eq__ 等 |
常用语法速查
| 需求 | 代码示例 |
|---|---|
| 定义类 | class Person: |
| 创建对象 | p = Person() |
| 定义类属性 | species = "human" |
| 定义实例属性 | self.name = name |
| 定义实例方法 | def speak(self): print("Hello") |
| 定义类方法 | @classmethod def create(cls): |
| 定义静态方法 | @staticmethod def util(): |
| 私有属性 | self.__password = "xxx" |
| 构造方法 | def __init__(self, name): |
| 继承父类 | class Student(Person): |
| 调用父类构造方法 | super().__init__(name) |
| 方法重写 | def speak(self): pass |
| 运算符重载 | def __add__(self, other): |
面向对象三大特性总结
| 特性 | 定义 | 实现方式 | 作用 |
|---|---|---|---|
| 封装 | 隐藏内部细节,提供公开接口 | 私有属性 + 公有方法 | 保护数据安全,降低耦合 |
| 继承 | 子类自动拥有父类成员 | class Sub(Parent): |
代码复用,建立层次关系 |
| 多态 | 同一接口,不同实现 | 方法重写 + 父类引用 | 扩展性强,接口统一 |
常见错误排查指南
| 错误现象 | 大概率原因 | 解决方案 |
|---|---|---|
TypeError: __init__() missing 1 required positional argument |
创建对象时未传入必要参数 | 检查构造方法的参数,传入正确的参数 |
AttributeError: 'X' object has no attribute 'y' |
访问了不存在的属性或方法 | 检查属性名是否正确(注意拼写和大小写) |
TypeError: method() takes 0 positional arguments but 1 was given |
定义实例方法时忘了写 self |
实例方法的第一个参数必须为 self |
AttributeError: 'super' object has no attribute 'xxx' |
父类中不存在该方法 | 确认父类是否有该方法,或 MRO 顺序是否符合预期 |
| 私有属性在外部无法访问 | 双下划线 __ 开头的属性是私有的 |
添加公有方法(getter/setter)来访问和修改 |
TypeError: unsupported operand type(s) for + |
自定义类的对象不支持 + 运算 |
添加 __add__ 方法重载运算符 |
学习/生产最佳实践
- 类名使用大驼峰命名法:如
Person、StudentInfo,方法与属性使用小写 + 下划线。 - 善用封装保护数据:将关键属性设为私有,通过公有方法访问和修改。数据校验放在 setter 方法中。
- 优先使用组合而非多继承:多继承虽然功能强大,但容易导致 MRO 混乱。在复杂场景中,优先考虑“组合”(将一个类的实例作为另一个类的属性)。
- 在每个类中添加
__str__()方法:便于调试和打印对象信息。
class Person:
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
- 合理使用
super():在子类中调用父类方法时,使用super()而非显式写出父类名,这样在多继承中能保证 MRO 正确。 - 方法重写时遵循 LSP:子类重写父类方法时,应保持方法签名(参数个数、返回类型)兼容,不改变方法的预期行为。
🔜 下期预告
本期我们系统学习了面向对象编程——从类与对象的定义、属性与方法,到封装、继承、多态三大特性,再到运算符重载等高级特性。通过“好友管理系统”和“人机猜拳游戏”两个实战项目,将面向对象思想应用到实际场景中。
下一期 ,我们将进入 异常处理 的学习——程序运行过程中不可避免地会出现错误,如何优雅地捕获和处理这些异常,是编写健壮程序的关键。我们将学习 try-except 语句、else 与 finally 子句、raise 主动抛出异常、assert 断言以及自定义异常类,让程序在面对错误时不再“崩溃”,而是能够“优雅地恢复”。敬请期待!
更多推荐


所有评论(0)