Python面向对象编程
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
🎯 核心思想: 不关心
obj是Student还是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 → 摧毁!
知识点复盘
| 用到的知识 | 在哪里 |
|---|---|
| 继承 | RedBirds、YellowBirds、BlueBirds 继承 Birds |
| 方法重写 | 每种小鸟重写了 fly() 和 call() |
| super() | 子类 __init__ 中调用父类构造函数 |
| 多态 | be_attacked(bird) 方法,传入不同类型的小鸟 |
| 封装 | 障碍物的 strength 通过方法操作 |
十、总结
| 知识点 | 核心概念 | 关键字/语法 |
|---|---|---|
| 动态属性 | 运行时给对象/类添加属性和方法 | 对象.属性 = 值、types.MethodType |
| 私有化 | 保护数据不被随意访问和修改 | __属性、@property |
| 单继承 | 子类继承父类的非私有成员 | class 子类(父类) |
| 多继承 | 一个子类继承多个父类 | class 子类(父类1, 父类2) |
| 方法复用 | 子类中调用父类方法 | super().方法()、父类.方法(self) |
| MRO | 多继承时的方法查找顺序 | 类.__mro__、C3 线性化 |
| 方法重写 | 子类用自己的逻辑替换父类方法 | 定义同名方法即可覆盖 |
| 多态 | 同一接口,不同实现 | 鸭子类型、isinstance() |
🎯 学习建议: OOP 的四大特性——封装、继承、多态、抽象,在 Python 中都有体现。建议多动手写代码,尤其是实战项目(如愤怒的小鸟),把知识点串起来才能真正掌握。
如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 评论 💬 支持一下!
更多推荐


所有评论(0)