Python 面向对象编程(OOP)全攻略:从封装到多态,一篇搞定

📌 本文系统梳理 Python 面向对象编程的核心知识点,涵盖动态属性操作、私有化与封装、单继承与多继承、方法重写与复用、MRO 解析顺序、多态,最后通过一个"愤怒的小鸟"实战项目串联所有知识。适合 Python OOP 初学者收藏学习。


📑 目录


一、动态添加属性与方法

Python 是动态语言,允许在运行时给对象或类添加属性和方法,这是静态语言(如 Java、C++)做不到的。

1.1 动态给实例添加属性

class Person:
    def __init__(self, name=None):
        self.name = name

p = Person('zs')
print(p.name)       # zs

p.age = 20          # 动态添加实例属性
print(p.age)        # 20

1.2 动态给类添加属性

class Person:
    def __init__(self, name=None):
        self.name = name

p = Person('zs')

Person.home = 'earth'   # 动态给类添加类属性
print(Person.home)      # earth
print(p.home)           # earth(实例也能访问类属性)

1.3 动态给实例添加方法

直接赋值普通函数可以调用,但缺少 self 绑定。要添加真正的实例方法,需要借助 types.MethodType

import types

class Person:
    def __init__(self, name=None):
        self.name = name

def eat(self):
    print(f'{self.name} eating')

p1 = Person('zs')
p1.eat = types.MethodType(eat, p1)  # 绑定实例对象
p1.eat()  # zs eating

1.4 动态给类添加方法

通过 @classmethod@staticmethod 装饰器,可以直接给类添加方法:

class Person:
    home = 'earth'
    def __init__(self, name=None):
        self.name = name

@classmethod
def drink(cls):
    print(f'来自{cls.home}')

@staticmethod
def static_method():
    print('static_method')

Person.drink = drink
Person.drink()          # 来自earth

Person.static = static_method
Person.static()         # static_method

1.5 动态删除属性和方法

# 方式一:del 对象.属性名
del p1.name

# 方式二:delattr(对象, 属性名)
delattr(p1, 'name')

1.6 __slots__ 限制实例属性

__slots__ 可以限制实例对象只能添加指定的属性和方法,防止随意动态添加:

class Person:
    __slots__ = ('name', 'age')  # 只允许 name 和 age

    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person('zs', 22)
# p.email = 'test@test.com'  # ❌ AttributeError!不在 __slots__ 中

⚠️ __slots__ 仅限制实例属性和实例方法,不限制类属性。


二、私有属性与封装

2.1 为什么要私有化?

不私有化的问题:

user.password = 'abc123'
# 任何代码都能:print(user.password)  → 直接看到密码!
# 任何代码都能:user.password = ''    → 直接篡改密码!

私有化后:

  • 只能通过 check_password() 验证
  • 只能通过 change_password() 修改
  • 密码本身永远看不到

🎯 私有化 = 给数据上一把锁。 不私有化谁都能改 → 不安全;私有化后通过方法操作 → 安全、可控。

2.2 私有化方式

方式 说明 强制访问
_属性名(单下划线) 约定俗成的"请勿访问",无强制性 直接访问
__属性名(双下划线) 底层通过改名实现私有化 _类名__属性名
class Person:
    def __init__(self, name, age, user, password):
        self.name = name
        self.age = age
        self._user = user           # 约定私有,外部可以访问
        self.__password = password  # 强制私有,外部不能直接访问

p = Person('zs', 22, 'admin', 'abc11')
print(p._user)              # admin ✅ 可以访问
# print(p.__password)       # ❌ AttributeError
print(p._Person__password)  # abc11 ✅ 强制访问(不推荐)

2.3 实战场景:密码管理

class User:
    def __init__(self, username, password):
        self.username = username
        self.__password = password

    def check_password(self, password):
        return password == self.__password

    def change_password(self, old_pas, new_pas):
        if old_pas == self.__password:
            self.__password = new_pas
            print('密码成功修改')
        else:
            print('旧密码错误')

user = User('username', 'password')
print(user.check_password('password'))   # True
user.change_password('password', 'lwhlwh')
print(user.check_password('lwhlwh'))     # True

2.4 实战场景:银行余额保护

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def deposit(self, amount):
        """存入金额"""
        if amount < 0:
            print('存款金额必须大于0')
            return
        self.__balance += amount
        print(f'存入金额为{amount},当前余额为{self.__balance}')

    def withdraw(self, amount):
        """取款金额"""
        if amount < 0:
            print('取款金额必须大于0')
            return
        if amount > self.__balance:
            print('余额不足')
            return
        self.__balance -= amount
        print(f'取款金额为{amount},当前余额为{self.__balance}')

    def get_balance(self):
        """查询金额"""
        return self.__balance

account = BankAccount('zs', 100)
account.deposit(50)          # 存入金额为50,当前余额为150
account.withdraw(30)         # 取款金额为30,当前余额为120
print(account.get_balance()) # 120

2.5 @property 装饰器

使用 @property 可以让方法像属性一样调用,更优雅地实现私有属性的访问和设置:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    @property
    def age(self):
        """getter:获取年龄"""
        return self.__age

    @age.setter
    def age(self, new_age):
        """setter:设置年龄(带校验)"""
        if 0 <= new_age <= 150:
            self.__age = new_age
        else:
            print('年龄不合法')

p = Person('zs', 22)
print(p.age)       # 22(像属性一样调用,实际上是调用了方法)
p.age = 30         # 像属性一样赋值,实际上触发了 setter
print(p.age)       # 30
p.age = 200        # 年龄不合法

⚠️ 注意: 装饰器的名字不要和变量重名,否则可能导致无限递归。


三、单继承

继承让子类自动获得父类的所有非私有成员,实现代码复用。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def drink(self):
        print(self.name, 'drink')

class Student(Person):
    def work_place(self):
        users = 'student'
        print(self.name, f'worker position {users}')

class Teacher(Person):
    def work_place(self):
        users = 'teacher'
        print(self.name, f'worker position {users}')

# Student 继承了 Person 的 drink 方法
s1 = Student('zs', '18')
s1.work_place()  # zs worker position student
s1.drink()        # zs drink(来自父类)

# Teacher 同样继承了 Person
t1 = Teacher('lzs', '20')
t1.work_place()   # lzs worker position teacher
t1.drink()         # lzs drink

💡 继承语法: class 子类名(父类名):


四、多继承

Python 支持一个子类同时继承多个父类

class Person:
    home = 'earth'
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Y_Student(Person):
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    color = 'yellow'
    def run(self):
        print("runing...")

class Student(Person):
    def work_place(self):
        users = 'student'
        print(self.name, f'worker position {users}')

class ChineseStudent(Y_Student, Student):
    country = 'China'

y1 = ChineseStudent('zs', '一年级')
print(y1.name)      # zs
print(y1.grade)     # 一年级
print(y1.country)   # China
print(y1.home)      # earth(继承自 Person)
y1.run()             # runing...(继承自 Y_Student)

💡 多继承语法: class 子类名(父类1, 父类2, ...):


五、复用父类的方法

子类中调用父类方法有 3 种方式

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(f'eating {self.name}')

class Student(Person):
    def __init__(self, name, age, gender):
        super().__init__(name, age)  # 推荐:用 super() 调用父类构造
        self.gender = gender

    def study(self):
        super().eat()           # 方式1:super() 调用(最推荐)
        Person.eat(self)        # 方式2:指定类名调用
        self.eat()              # 方式3:通过 self 调用(沿 MRO 查找)
方式 语法 特点
super().方法() 推荐 自动沿 MRO 链查找下一个父类
父类名.方法(self) 指定 明确指定调用哪个父类
self.方法() 隐式 沿 MRO 顺序查找,可能调到自己

六、方法的解析顺序(MRO)

当存在多继承时,Python 通过 MRO(Method Resolution Order) 决定方法查找顺序。

class Chinese(Student, YellowRace):
    country = "China"

print(Chinese.__mro__)
# (<class 'Chinese'>, <class 'Student'>, <class 'YellowRace'>, <class 'Person'>, <class 'object'>)

Python 采用 C3 线性化算法,查找顺序为:

当前类 → 父类1 → 父类2 → ... → object

super()**kwargs 的配合

在多继承中,super() 配合 **kwargs 可以实现链式参数传递

class Flyable:
    def __init__(self, name_fly=None, **kwargs):
        self.name_fly = name_fly
        self.can_fly = True
        super().__init__(**kwargs)  # 把剩余参数传给下一个

class Swimming:
    def __init__(self, name_swim=None, **kwargs):
        self.name_swim = name_swim
        self.can_swim = True
        super().__init__(**kwargs)

class Duck(Flyable, Swimming):
    def __init__(self, name):
        super().__init__(name_fly=name, name_swim=name)

duck = Duck('唐老鸭')
print(duck.name_fly)   # 唐老鸭
print(duck.name_swim)  # 唐老鸭

调用过程解析:

Duck 传 name_fly='唐老鸭' + name_swim='唐老鸭'
        ↓
Flyable 收到全部参数
  ├── name_fly='唐老鸭'   → 留下自己用 ✅
  └── **kwargs={'name_swim': '唐老鸭'}  → 传给下一个
        ↓
Swimming 收到剩余参数
  ├── name_swim='唐老鸭'  → 留下自己用 ✅
  └── **kwargs={}          → 空了,结束

七、方法的重写

子类对父类的同名方法进行修改,用自己的逻辑替换父类逻辑。

7.1 基础重写

class Animal:
    def speak(self):
        print('动物在叫')

class Dog(Animal):
    def speak(self):
        print('汪汪汪')  # 重写父类方法

dog = Dog()
dog.speak()  # 汪汪汪(调用 Dog 重写后的方法)

💡 调用顺序: 先在当前类中找 → 没有再去父类中找。

7.2 重写应用场景:图形面积

import math

class Shape:
    def area(self):
        print('计算面积')  # 太笼统

    def describe(self):
        print('这是一个形状')

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):  # 重写:圆有自己的公式
        return math.pi * self.radius ** 2

    def describe(self):
        print(f'这是一个圆,半径={self.radius}')

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):  # 重写:长方形有自己的公式
        return self.width * self.height

    def describe(self):
        print(f'这是一个长方形,宽={self.width},高={self.height}')

circle = Circle(2)
print(circle.area())    # 12.566...
circle.describe()       # 这是一个圆,半径=2

rectangle = Rectangle(3, 4)
print(rectangle.area()) # 12
rectangle.describe()    # 这是一个长方形,宽=3,高=4

7.3 重写并扩展(super())

子类想在父类方法基础上增加新功能,而不是完全替换:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f'{self.name} 在叫')

class Dog(Animal):
    def __init__(self, name, breed, age):
        super().__init__(name)    # 继承父类的属性
        self.breed = breed        # 增加自己的属性
        self.age = age

    def speak(self):
        super().speak()           # 先调用父类的方法
        print(f'{self.name}:汪汪汪!')  # 再扩展

dog = Dog('wc', '金毛', 3)
print(dog.name)   # wc
print(dog.breed)  # 金毛
dog.speak()
# wc 在叫
# wc:汪汪汪!

7.4 __init__ 的重写

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(f'姓名:{self.name},年龄:{self.age}')

# 方式1:扩展父类 __init__(推荐)
class Student(Person):
    def __init__(self, name, age, school):
        super().__init__(name, age)  # 调用父类初始化
        self.school = school         # 新增属性

    def show(self):
        super().show()               # 先调用父类的 show
        print(f'学校:{self.school}') # 再补充

# 方式2:完全替换 __init__
class Teacher(Person):
    def __init__(self, name, age, subject):
        self.name = name             # 自己写
        self.age = age
        self.subject = subject

    def show(self):
        print(f'老师:{self.name}{self.age}岁,教{self.subject}')

s = Student('张三', 20, 'Python大学')
s.show()
# 姓名:张三,年龄:20
# 学校:Python大学

t = Teacher('李老师', 35, 'Python')
t.show()
# 老师:李老师,35岁,教Python

八、多态

多态指同一个方法调用,不同对象有不同的行为。Python 的多态基于鸭子类型——不关心对象是什么类型,只关心它有没有对应的方法。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(self.name, "is eaten", self.age)

class Student(Person):
    pass

class Teacher(Person):
    pass

def new_person(num):
    if num == 1:
        obj = Student('lwh', 20)
    else:
        obj = Teacher('teacher', 20)
    obj.eat()  # 同一个方法调用,不同对象行为不同

new_person(1)  # lwh is eaten 20

🎯 核心思想: 不关心 objStudent 还是 Teacher,只要它有 eat() 方法就能调用。这就是多态。


九、综合实战:愤怒的小鸟

下面通过一个"愤怒的小鸟"小游戏,综合运用继承、封装、多态、方法重写等所有知识点:

class Birds:
    """鸟类基类"""
    def __init__(self, name, color, skill_description):
        self.name = name
        self.color = color
        self.skill_description = skill_description

    def fly(self):
        print(f'{self.name} fly...')

    def call(self):
        print(f'{self.name} call...')

    def use_skill(self):
        print(f'{self.name} use skills: {self.skill_description}')


class RedBirds(Birds):
    """红色小鸟 —— 暴力冲击"""
    def __init__(self):
        super().__init__('小红', '红色', '暴力冲击力量,速度提升10%')

    def fly(self):  # 重写
        print(f'{self.name} fly 以稳定速度飞行')

    def call(self):  # 重写
        print(f'{self.name} call gagaga')


class YellowBirds(Birds):
    """黄色小鸟 —— 瞬间加速"""
    def __init__(self):
        super().__init__('小黄', '黄色', '瞬间加速,穿墙')

    def fly(self):
        print(f'{self.name} fly 快速飞行')

    def call(self):
        print(f'{self.name} call gagjaja')


class BlueBirds(Birds):
    """蓝色小鸟 —— 分裂攻击"""
    def __init__(self):
        super().__init__('小蓝', '蓝色', '分裂成三只鸟,分散攻击')

    def fly(self):
        print(f'{self.name} fly 优雅飞行')

    def call(self):
        print(f'{self.name} call jajajaja')


class Obstacle:
    """障碍物类"""
    def __init__(self, name, strength):
        self.name = name
        self.strength = strength

    def be_attacked(self, bird):
        """被攻击 —— 体现多态:不同小鸟造成不同伤害"""
        bird.use_skill()
        bird.fly()

        if isinstance(bird, RedBirds):
            damage = 80
        elif isinstance(bird, YellowBirds):
            damage = 50
        elif isinstance(bird, BlueBirds):
            damage = 30 * 3  # 分裂成3只

        self.strength -= damage
        if self.strength <= 0:
            print(f'{self.name} 被摧毁了!')
        else:
            print(f'{self.name} 没有被摧毁,还剩余血量 {self.strength}')


if __name__ == '__main__':
    red = RedBirds()
    blue = BlueBirds()
    yellow = YellowBirds()

    stone = Obstacle('石头堡垒', 200)
    tree = Obstacle('木头堡垒', 150)

    stone.be_attacked(red)      # 伤害 80,剩余 120
    stone.be_attacked(blue)     # 伤害 90,剩余 30
    stone.be_attacked(yellow)   # 伤害 50,剩余 -20 → 摧毁!

    tree.be_attacked(red)       # 伤害 80,剩余 70
    tree.be_attacked(blue)      # 伤害 90,剩余 -20 → 摧毁!

知识点复盘

用到的知识 在哪里
继承 RedBirdsYellowBirdsBlueBirds 继承 Birds
方法重写 每种小鸟重写了 fly()call()
super() 子类 __init__ 中调用父类构造函数
多态 be_attacked(bird) 方法,传入不同类型的小鸟
封装 障碍物的 strength 通过方法操作

十、总结

知识点 核心概念 关键字/语法
动态属性 运行时给对象/类添加属性和方法 对象.属性 = 值types.MethodType
私有化 保护数据不被随意访问和修改 __属性@property
单继承 子类继承父类的非私有成员 class 子类(父类)
多继承 一个子类继承多个父类 class 子类(父类1, 父类2)
方法复用 子类中调用父类方法 super().方法()父类.方法(self)
MRO 多继承时的方法查找顺序 类.__mro__、C3 线性化
方法重写 子类用自己的逻辑替换父类方法 定义同名方法即可覆盖
多态 同一接口,不同实现 鸭子类型、isinstance()

🎯 学习建议: OOP 的四大特性——封装、继承、多态、抽象,在 Python 中都有体现。建议多动手写代码,尤其是实战项目(如愤怒的小鸟),把知识点串起来才能真正掌握。


如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 评论 💬 支持一下!

更多推荐