Day 09:面向对象三大特性
🎯 学习目标:深入掌握封装、继承、多态,完成《愤怒的小鸟》综合案例
1. 封装(Encapsulation)
1.1 私有化属性和方法
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 双下划线 __ → 私有属性
def deposit(self, amount):
if amount <= 0:
raise ValueError("存款金额必须为正数")
self.__balance += amount
print(f"存入 {amount},余额 {self.__balance}")
def withdraw(self, amount):
if amount <= 0:
raise ValueError("取款金额必须为正数")
if amount > self.__balance:
raise ValueError("余额不足")
self.__balance -= amount
print(f"取出 {amount},余额 {self.__balance}")
def get_balance(self):
return self.__balance # 通过方法安全访问
account = BankAccount("张三", 1000)
# print(account.__balance) # ❌ AttributeError
print(account.get_balance()) # ✅ 1000
# account._BankAccount__balance # 仍然可以访问,但强烈不推荐
1.2 @property 装饰器
class Student:
def __init__(self, name, score=0):
self.name = name
self.__score = score
@property
def score(self):
"""读:像访问属性一样调用方法"""
return self.__score
@score.setter
def score(self, value):
"""写:赋值时自动校验"""
if not 0 <= value <= 100:
raise ValueError("分数必须在 0~100 之间")
self.__score = value
@score.deleter
def score(self):
"""删:del 时触发"""
print("分数被删除")
self.__score = 0
s = Student("张三")
s.score = 85 # 自动调用 setter(带校验)
print(s.score) # 85(自动调用 getter)
# s.score = 150 # ❌ ValueError:分数必须在 0~100 之间
del s.score # "分数被删除",score 重置为 0
@property 的好处
- 像属性一样使用(
obj.attr而不是obj.get_attr())
- 自动做数据校验
- 保护内部实现,后续可改实现而不影响外部调用
2. 继承(Inheritance)
2.1 单继承
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
def __str__(self):
return f"{self.name}"
class Dog(Animal): # Dog 继承 Animal
def speak(self): # 方法重写(override)
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵!"
d = Dog("旺财")
c = Cat("咪咪")
print(f"{d}:{d.speak()}") # "旺财:汪汪!"
print(f"{c}:{c.speak()}") # "咪咪:喵喵!"
2.2 super 调用父类
class Vehicle:
def __init__(self, brand, speed=0):
self.brand = brand
self.speed = speed
def info(self):
return f"{self.brand},速度 {self.speed}km/h"
class Car(Vehicle):
def __init__(self, brand, doors=4, speed=0):
super().__init__(brand, speed) # 调用父类 __init__
self.doors = doors
def info(self):
base = super().info() # 调用父类方法
return f"{base},{self.doors}门"
2.3 多继承与 MRO
class FlyMixin:
def fly(self):
return f"{self.name} 会飞!"
class SwimMixin:
def swim(self):
return f"{self.name} 会游泳!"
class Duck(Animal, FlyMixin, SwimMixin):
def speak(self):
return "嘎嘎!"
duck = Duck("唐老鸭")
print(duck.speak()) # "嘎嘎!"
print(duck.fly()) # "唐老鸭 会飞!"(来自 FlyMixin)
print(duck.swim()) # "唐老鸭 会游泳!"(来自 SwimMixin)
# MRO(Method Resolution Order):方法搜索顺序
print(Duck.__mro__)
# Duck → Animal → FlyMixin → SwimMixin → object
2.4 方法重写
class Parent:
def greet(self):
print("父类的问候")
def work(self):
print("父类工作")
class Child(Parent):
def greet(self): # 完全重写
print("子类的问候")
def work(self): # 扩展父类功能
super().work() # 先调用父类
print("子类额外的工作") # 再加自己的
3. 多态(Polymorphism)
# 多态:同一接口,不同实现
class Bird:
def __init__(self, name):
self.name = name
def attack(self):
return f"{self.name} 发起攻击"
class RedBird(Bird):
def attack(self):
return f"{self.name}(红色小鸟)冲撞攻击!💥"
class BlackBird(Bird):
def attack(self):
return f"{self.name}(黑色小鸟)爆炸攻击!💣💥"
class WhiteBird(Bird):
def attack(self):
return f"{self.name}(白色小鸟)投弹攻击!🥚"
# 多态体现:同一个 attack 函数,对不同鸟产生不同行为
def launch_bird(bird):
print(bird.attack())
# 不管什么类型的鸟,只要它有 attack 方法就能用
birds = [RedBird("小红"), BlackBird("小黑"), WhiteBird("小白")]
for bird in birds:
launch_bird(bird)
# 小白(白色小鸟)投弹攻击!🥚
鸭子类型(Duck Typing)
“如果它走路像鸭子,叫起来像鸭子,那它就是鸭子”
Python 不检查类型,只检查有没有对应的方法。
这就是为什么 print 能接受任何对象——只要它有
__str__方法。
4. 愤怒的小鸟综合案例
import random
class Bird:
"""鸟基类"""
def __init__(self, name, damage, speed):
self.name = name
self.__damage = damage # 封装:私有属性
self.__speed = speed
@property
def damage(self):
return self.__damage
@property
def speed(self):
return self.__speed
def attack(self, target):
"""攻击目标,子类可重写"""
print(f"{self.name} 以速度 {self.speed} 飞向 {target}!")
return self.__damage
def __str__(self):
return f"{self.name}(伤害:{self.__damage}, 速度:{self.__speed})"
class RedBird(Bird):
"""红色小鸟:无特殊技能但可靠"""
def attack(self, target):
print(f"🔴 {self.name} 加速冲撞!")
return super().attack(target) # 父类伤害
class BlackBird(Bird):
"""黑色小鸟:爆炸攻击,伤害 x2"""
def attack(self, target):
print(f"⚫ {self.name} 俯冲爆炸!")
base = super().attack(target)
return base * 2 # 伤害翻倍
class WhiteBird(Bird):
"""白色小鸟:投弹攻击,伤害 x1.5"""
def attack(self, target):
print(f"⚪ {self.name} 高空投弹!")
base = super().attack(target)
return int(base * 1.5)
# === 游戏模拟 ===
class PigFort:
"""猪的堡垒"""
def __init__(self, hp=100):
self.hp = hp
def take_damage(self, damage):
self.hp -= damage
return self.hp > 0 # True 表示还活着
print("🎯 愤怒的小鸟 — 攻击猪堡垒!")
print("=" * 50)
fort = PigFort(hp=120)
birds = [
RedBird("小红", 30, 10),
BlackBird("小黑", 25, 8),
WhiteBird("小白", 20, 12),
]
for bird in birds:
print(f"\n{bird} 出战!")
damage = bird.attack("猪堡垒")
alive = fort.take_damage(damage)
print(f"造成 {damage} 点伤害,堡垒剩余 HP:{fort.hp}")
if not alive:
print("🎉 堡垒被摧毁!")
break
else:
print(f"\n堡垒幸存,剩余 {fort.hp} HP")
速记卡(面试闪卡)
Q1:一句话讲清「Day 09:面向对象三大特性」到底是什么?
A:封装隐藏实现细节、继承复用父类代码、多态让同一接口不同实现,是面向对象编程的三大基石。
Q2:1. 封装(Encapsulation) —— 怎么理解?
A:封装把数据和操作数据的方法绑一起并隐藏细节。__balance 双下划线是私有属性——但 Python 只是做名称改写(Name Mangling,英文 名称改写)变成 _BankAccount__balance,并非真私有,仍能访问只是不推荐。@property 让方法能像属性一样 obj.score 访问,赋值时自动跑 setter 做校验。比喻:余额藏在系统里,你只能经 ATM(方法)存取。
Q3:2. 继承(Inheritance) —— 怎么理解?
A:继承让子类复用父类代码:class Dog(Animal) 单继承可重写方法;super().__init__() 动态调父类(多继承按 MRO 顺序找下一个,别硬编码父类名)。多继承 class Duck(Animal, FlyMixin, SwimMixin) 用 C3 线性化算 MRO(方法解析顺序,英文 Method Resolution Order),继承顺序不同结果不同。
Q4:3. 多态(Polymorphism) —— 怎么理解?
A:多态(Polymorphism)是”同一接口,不同实现”:launch_bird(bird) 不管传 RedBird 还是 BlackBird,只要它有 attack 方法就各自产生不同行为。背后是鸭子类型(Duck Typing):“走路像鸭子、叫像鸭子就是鸭子”——Python 不查类型、只查有没有对应方法,所以 print 能接受任何有 __str__ 的对象。
Q5:常见坑点(伪私有 / 菱形 / 可变默认) —— 怎么理解?
A:高频坑:① 私有是名称改写非真私有(_Class__attr 仍能访问);② 多继承菱形问题靠 C3 算 MRO,D(B,C) 与 D(C,B) 顺序不同结果不同;③ super() 别硬编码父类否则多继承重复调用;④ 子类覆盖父类 @property 后直接赋值会 AttributeError(需定义 setter);⑤ 类里可变默认参数 items=[] 所有实例共享,要用 None。
Q6:核心速记主线有哪些?
-
封装:私有属性 / @property / 名称改写(非真私有)
-
继承:单继承重写 / super 调父类 / 多继承 MRO(C3 算法)
-
多态:方法重写 + 鸭子类型(不查类型查方法)
-
坑:伪私有 / 菱形 MRO / 可变默认参数
口诀
A:封装藏起加 property,继承复用 super 连;
多态靠鸭子类型,MRO 定序解多继;
伪私有是改名,可变默认用 None;
OOP 三特性,复用扩展不为难。
相关链接
面向对象三大特性关系图
graph TD A[面向对象三大特性] --> B["封装<br/>隐藏内部实现"] A --> C["继承<br/>代码复用"] A --> D["多态<br/>接口统一"] B --> B1["私有属性<br/>__balance"] B --> B2["@property<br/>受控访问"] B --> B3["名称改写<br/>_ClassName__attr"] C --> C1["单继承<br/>class Child Parent"] C --> C2["多继承<br/>class Child Parent1 Parent2"] C --> C3["MRO方法解析顺序<br/>C3算法"] D --> D1["方法重写<br/>子类重写父类方法"] D --> D2["鸭子类型<br/>只要会叫就是鸭子"] D --> D3["super调用父类<br/>supermethod"] style A fill:#e1f5fe style B fill:#f3e5f5 style C fill:#e8f5e8 style D fill:#fff3e0
常见坑点
1. 私有属性的“伪私有”
class MyClass:
def __init__(self):
self.__private = 1
obj = MyClass()
print(obj._MyClass__private) # 1 ← 仍然可以访问
-
原理:Python的私有是名称改写(Name Mangling),不是真正的私有
-
目的:避免子类意外覆盖父类私有属性
-
最佳实践:单下划线
_private表示“内部使用”,双下划线__private表示“子类不要碰”
2. 多继承的菱形问题
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
pass
d = D()
d.method() # B ← 按MRO顺序:D→B→C→A
print(D.__mro__) # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
-
问题:多继承时,方法调用顺序如何确定?
-
解决:Python用C3线性化算法计算MRO(方法解析顺序)
-
坑点:继承顺序影响MRO,
class D(B, C)和class D(C, B)结果不同
3. super() 的误用
class Parent:
def __init__(self):
print("Parent init")
class Child(Parent):
def __init__(self):
super().__init__() # ✅ 正确调用父类
print("Child init")
c = Child() # 输出:Parent init → Child init
-
错误:
Parent.__init__(self)← 硬编码父类,多继承时会重复调用 -
正确:
super().__init__()← 动态查找下一个类 -
注意:
super()在多继承中按MRO顺序调用
4. @property 与继承
class Parent:
@property
def value(self):
return "parent"
class Child(Parent):
@property
def value(self):
return "child"
c = Child()
print(c.value) # child ← 子类覆盖父类property
-
坑点:子类覆盖父类property时,不能直接赋值
-
错误:
c.value = "new"← AttributeError(没有setter) -
解决:子类property需要定义setter,或用
@value.setter
5. __str__ vs __repr__
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"({self.x}, {self.y})" # 用户友好
def __repr__(self):
return f"Point({self.x}, {self.y})" # 开发者友好
p = Point(1, 2)
print(str(p)) # (1, 2) ← 调用__str__
print(repr(p)) # Point(1, 2) ← 调用__repr__
print(p) # (1, 2) ← 优先__str__
-
区别:
__str__面向用户,__repr__面向开发者 -
最佳实践:至少实现
__repr__,调试时更有用
6. 可变默认参数(与函数相同)
class MyClass:
def __init__(self, items=[]): # ❌ 可变默认参数
self.items = items
def add(self, item):
self.items.append(item)
a = MyClass()
a.add(1)
b = MyClass()
print(b.items) # [1] ← b共享了a的items!
- 解决:使用
None作为默认值
def __init__(self, items=None):
self.items = items if items is not None else []