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 的好处

  1. 像属性一样使用(obj.attr 而不是 obj.get_attr()
  1. 自动做数据校验
  1. 保护内部实现,后续可改实现而不影响外部调用

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 []
 

技术学习路线图 > 函数与 OOP