【Python系列课程】Python面向对象(下):封装、继承与多态

-





📊
:24分钟 | 关键词:Python面向对象、封装、继承、多态、super()、方法重写、MRO

#### 引言:面向对象的三大支柱

上一篇文章我们学了类和对象的基础——如何定义类、创建对象、使用属性和方法。但那只是面向对象的"语法",不是"思想"。

面向对象编程真正的威力在于三大特性:封装、继承、多态

| 特性 | 解决的问题 | 核心机制 |
| 封装 | 如何保护数据不被随意修改? | 私有属性、私有方法 |
| 继承 | 如何复用已有代码? | 子类继承父类的属性和方法 |
| 多态 | 如何让不同对象对同一消息做出不同响应? | 方法重写、鸭子类型 |
#### 一、封装:保护你的数据

##### 1.1 为什么需要封装?

看一个没有封装的例子:

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

account = BankAccount('小明', 10000)
account.balance = -50000 # 直接修改!余额变负数了!
print(account.balance) # -50000 —— 这合理吗?
`
任何人都可以直接修改 balance,没有任何保护。封装就是解决这个问题的——隐藏内部实现细节,只暴露安全的接口

##### 1.2 Python 的访问控制

Python 没有 Java/C++ 那样的 private/public 关键字。它通过命名约定来实现访问控制:

| 命名方式 | 含义 | 外部访问 |
| name | 公有属性/方法 | ✅ 可以 |
| _name | "保护"属性/方法(约定,不强制) | ⚠️ 可以但不建议 |
| __name | 私有属性/方法(名称改写) | ❌ 不能直接访问 |
##### 1.3 私有属性和方法

在属性名或方法名前加两个下划线,就变成了私有的:

`class Person:
school = '深兰教育'
__eat = 'rice' # 私有类属性

def __init__(self, name, age):
self.name = name # 公有实例属性
self.__age = age # 私有实例属性

def get_up(self): # 公有方法
print(f'{self.name}起床了!')

def __sleep(self): # 私有方法
print(f'{self.name}睡觉了!')

# 通过公有方法访问私有属性
def get_age(self):
return self.__age

def set_age(self, age):
if 0 < age < 150:
self.__age = age
else:
print('年龄不合法!')

# 通过公有方法调用私有方法
def call_sleep(self):
self.__sleep()

p = Person('张三', 19)

公有属性和方法:可以直接访问


print(p.name) # '张三'
p.get_up() # 张三起床了!

私有属性和方法:不能直接访问


print(p.__age) # AttributeError!


p.__sleep() # AttributeError!


print(Person.__eat) # AttributeError!

正确方式:通过公有方法间接访问


print(p.get_age()) # 19
p.call_sleep() # 张三睡觉了!
`
##### 1.4 名称改写机制(Name Mangling)

Python 的"私有"不是真正的私有,而是一种名称改写

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

p = Person('张三')

p.__name # AttributeError


print(p._Person__name) # '张三' —— 还是能访问到!
`
Python 把 __name 改写成了 _类名__name。这只是一个防止意外访问的机制,而不是安全机制。

Python 社区的态度:我们都是成年人,约定大于强制。如果你真的想访问私有属性,Python 不会阻止你——但你应该知道自己在做什么。

>
📸 [图1:Python 名称改写机制图解]

建议配图:左侧展示类定义中的 self.__age,右侧展示实例的 __dict__ 中实际存储的是 _Person__age。用箭头标注名称改写的规则:__属性名_类名__属性名。标注"外部直接访问 __age 会报 AttributeError"。

##### 1.5 封装的最佳实践

`class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 私有属性

@property
def balance(self):
"""余额——只读属性"""
return self.__balance

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

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

使用


account = BankAccount('小明', 10000)
account.deposit(5000) # 存款成功,当前余额:15000
account.withdraw(2000) # 取款成功,当前余额:13000

account.__balance = -999 # 无效!不会影响真正的余额


print(account.balance) # 13000 —— 通过 property 安全访问
`
#### 二、继承:复用代码的最佳方式

##### 2.1 什么是继承?

继承让你基于已有的类创建新类。新类(子类)自动获得旧类(父类)的所有属性和方法。

`# 父类(基类)
class Person:
state = 'China'

@staticmethod
def eat():
print('吃饭')

@staticmethod
def speak():
print('说话')

子类:继承 Person


class Student(Person):
@staticmethod
def study():
print('读书')

class Worker(Person):
@staticmethod
def work():
print('搬砖')

子类自动拥有父类的方法


Student.study() # 读书(自己的方法)
Student.eat() # 吃饭(继承自 Person)
Student.speak() # 说话(继承自 Person)
print(Student.state) # China(继承自 Person)
`
##### 2.2 继承的查找顺序

当子类调用一个方法时,Python 按照 子类 → 父类 → 父类的父类 → … → object 的顺序查找:

`class Animal:
@staticmethod
def eat():
print('吃东西')

class Cat(Animal):
@staticmethod
def catch_mouse():
print('抓老鼠')

class Ragdoll(Cat):
@staticmethod
def cute():
print('卖萌')

继承链:Ragdoll → Cat → Animal → object


Ragdoll.cute() # 卖萌(自己的)
Ragdoll.catch_mouse() # 抓老鼠(从 Cat 继承)
Ragdoll.eat() # 吃东西(从 Animal 继承)
`
>
📸 [图2:单继承链查找顺序图解]

建议配图:画一个继承链:Ragdoll → Cat → Animal → object。标注方法调用时的查找顺序(从下往上),每个类标注自己的方法。用箭头标注查找方向。

##### 2.3 多重继承

Python 支持一个类继承多个父类:

`class Animal:
@staticmethod
def eat():
print('吃东西')

class Cat:
@staticmethod
def catch_mouse():
print('抓老鼠')

多重继承


class Ragdoll(Cat, Animal): # 注意顺序!
@staticmethod
def cute():
print('卖萌')

Ragdoll.cute() # 卖萌
Ragdoll.catch_mouse() # 抓老鼠
Ragdoll.eat() # 吃东西
`
多重继承的查找顺序遵循 MRO(Method Resolution Order,方法解析顺序)

`print(Ragdoll.__mro__)

(, , , )


`
Python 使用 C3 线性化算法 来确定 MRO,核心规则是:

子类优先于父类
- 按照继承列表中的顺序(从左到右)
- 所有父类都遵循同样的规则

`# 经典的多重继承示例
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): # B 在 C 前面
pass

d = D()
d.method() # 'B' —— 先找到 B
print(D.__mro__)

D → B → C → A → object


`
⚠️ 多重继承要谨慎使用。大多数情况下,单继承就足够了。多重继承会让代码变得难以理解和维护。

##### 2.4 方法重写(Override)

子类可以重新定义父类的方法:

`class Animal:
def __init__(self, food):
self.food = food

def eat(self):
print(f'动物吃{self.food}')

class Cat(Animal):
def eat(self): # 重写父类的 eat 方法
print(f'猫吃{self.food}') # 猫的行为不同于普通动物

c = Cat('鱼')
c.eat() # 猫吃鱼 —— 调用了子类的 eat,不是父类的
`
##### 2.5 super():调用父类的方法

重写父类方法后,如果想在子类中调用父类的版本,用 super()

`class Animal:
def eat(self):
print('吃东西')

class Cat(Animal):
def eat(self):
print('吃鱼')

class Ragdoll(Cat):
def eat(self):
print('喝咖啡')
super().eat() # 调用父类 Cat 的 eat
super(Cat, self).eat() # 调用 Cat 的父类 Animal 的 eat

rd = Ragdoll()
rd.eat()

输出:


喝咖啡


吃鱼


吃东西


`
##### 2.6 继承中的 __init__ 方法

当子类定义了 __init__,父类的 __init__ 不会自动调用:

`class A:
def __init__(self, name):
self.name = name
print(f'A.__init__: {self.name}')

情况1:子类没有 __init__,自动调用父类的


class B(A):
pass

b = B('张三') # A.__init__: 张三

情况2:子类有 __init__,父类的不自动调用


class C(A):
def __init__(self, name):
self.name = name # 手动初始化
print(f'C.__init__: {self.name}')

c = C('赵六') # C.__init__: 赵六(A 的 __init__ 没执行)

情况3:子类有 __init__,但用 super() 调用父类的


class D(A):
def __init__(self, name):
super().__init__('李四') # 先调用父类的 __init__
self.name = name # 再设置子类的属性
print(f'D.__init__: {self.name}')

d = D('王五')

A.__init__: 李四


D.__init__: 王五


`
最佳实践:如果你重写了 __init__,通常应该调用 super().__init__() 来确保父类的初始化逻辑被执行。

##### 2.7 isinstance() 和 issubclass()

两个用于类型判断的内置函数:

`class A:
pass

class B(A):
pass

a = A()
b = B()

isinstance(obj, class):判断 obj 是否是 class 的实例(考虑继承)


print(isinstance(b, B)) # True
print(isinstance(b, A)) # True —— 子类实例也是父类的实例
print(type(b) == A) # False —— type() 不考虑继承!

issubclass(cls, parent):判断 cls 是否是 parent 的子类


print(issubclass(B, A)) # True
print(issubclass(A, A)) # True —— 类被视作自身的子类
print(issubclass(B, object)) # True —— 所有类都是 object 的子类
`
#### 三、多态:同一个接口,不同的行为

##### 3.1 什么是多态?

多态的字面意思是"多种形态"——不同类的对象可以对同一个方法名做出不同的响应

`class Apple:
@staticmethod
def change():
return '啊~ 我变成了苹果汁!'

class Banana:
@staticmethod
def change():
return '啊~ 我变成了香蕉汁!'

class Mango:
@staticmethod
def change():
return '啊~ 我变成了芒果汁!'

class Juicer:
@staticmethod
def work(fruit):
"""榨汁机:只要 fruit 有 change() 方法,就能工作"""
print(fruit.change())

不同水果,同样的 change() 方法,不同的结果


Juicer.work(Apple()) # 啊~ 我变成了苹果汁!
Juicer.work(Banana()) # 啊~ 我变成了香蕉汁!
Juicer.work(Mango()) # 啊~ 我变成了芒果汁!
`
多态的核心Juicer.work() 不关心传入的是什么类型,只关心它有没有 change() 方法。这就是 Python 著名的 “鸭子类型”(Duck Typing):

>
如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。

##### 3.2 鸭子类型的实际应用

`# 所有有 draw() 方法的对象都可以传入
class Circle:
def draw(self):
print('画一个圆 ○')

class Square:
def draw(self):
print('画一个方框 □')

class Triangle:
def draw(self):
print('画一个三角形 △')

def render(shape):
"""渲染图形——不关心类型,只关心有没有 draw() 方法"""
shape.draw()

render(Circle()) # 画一个圆 ○
render(Square()) # 画一个方框 □
render(Triangle()) # 画一个三角形 △
`
这就是 Python 多态的精髓——不依赖继承关系,只依赖对象的行为

#### 四、面向对象综合示例:学生和老师的一天

让我们把学到的封装、继承、多态结合起来,完成一个完整的例子:

`class Person:
"""人类——基类"""
def __init__(self, name, age):
self.name = name
self.age = age
self.show_info() # 创建时自动介绍自己

def get_up(self):
print(f'{self.name}睁开眼睛 → 起身 → 穿好衣服')

def wash(self):
print(f'{self.name}刷牙 → 洗脸')

def eat(self):
print(f'{self.name}吃菜 → 扒饭')

def sleep(self):
print(f'{self.name}脱掉外套 → 躺下 → 闭上眼睛')

def show_info(self):
pass # 由子类实现

class Student(Person):
"""学生类"""
count = 0 # 类属性:统计学生人数

def __init__(self, name, age, grade):
self.grade = grade
super().__init__(name, age) # 调用父类 __init__
Student.count += 1

def show_info(self):
print(f'大家好!我是{self.name},今年{self.age}岁,在读{self.grade}!')

def login(self):
print(f'{self.name}输入账号密码 → 登录成功')

def study(self):
print(f'{self.name}看视频 → 查资料 → 写代码')

@classmethod
def publish(cls):
print(f'当前学生人数:{cls.count}')

class Teacher(Person):
"""老师类"""
count = 0

def __init__(self, name, age, department):
self.department = department
super().__init__(name, age)
Teacher.count += 1

def show_info(self):
print(f'大家好!我是{self.name},今年{self.age}岁,在{self.department}任职!')

def clock_in(self):
print(f'{self.name}录入指纹 → 打卡成功')

def work(self):
print(f'{self.name}授课 → 答疑 → 写代码')

@classmethod
def publish(cls):
print(f'当前老师人数:{cls.count}')

模拟一天


def simulate_day(person):
"""多态:不管是学生还是老师,都能执行一天的活动"""
person.get_up()
person.wash()
person.eat()

# 不同角色的特殊行为
if isinstance(person, Student):
person.login()
person.study()
elif isinstance(person, Teacher):
person.clock_in()
person.work()

person.eat()

if isinstance(person, Student):
person.study()
elif isinstance(person, Teacher):
person.work()

person.eat()
person.wash()
person.sleep()

使用


stu1 = Student('张三', 18, '高三')
stu2 = Student('李四', 16, '高一')
t1 = Teacher('老赵', 39, '教学部')

Student.publish() # 当前学生人数:2
Teacher.publish() # 当前老师人数:1

print('\n===== 张三的一天 =====')
simulate_day(stu1)

print('\n===== 老赵的一天 =====')
simulate_day(t1)
`
>
📸 [图3:Person-Student-Teacher 继承层次结构图]

建议配图:用 UML 类图展示 Person(基类)→ Student 和 Teacher(子类)的继承关系。标注每个类的属性(name、age、grade、department)和方法(get_up、wash、eat、sleep、show_info、study、work 等)。用不同颜色区分继承的方法和子类特有的方法。

#### 五、动手练习

练习 1:实现一个简单的形状类层次结构

`import math

class Shape:
"""形状基类"""
def area(self):
raise NotImplementedError('子类必须实现 area 方法')

def perimeter(self):
raise NotImplementedError('子类必须实现 perimeter 方法')

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

def area(self):
return math.pi self.radius * 2

def perimeter(self):
return 2 math.pi 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 perimeter(self):
return 2 * (self.width + self.height)

多态:同样的接口,不同的计算


shapes = [Circle(5), Rectangle(4, 6), Circle(3)]
for shape in shapes:
print(f'面积:{shape.area():.2f},周长:{shape.perimeter():.2f}')
`
练习 2:银行账户的继承

`# 在 BankAccount 基础上,实现以下子类:

SavingsAccount:有年利率,可以计算利息


CreditAccount:有信用额度,取款不能超过 余额+额度


`
#### 小结

这篇文章覆盖了面向对象编程的三大特性:

| 特性 | 核心机制 | 关键语法 |
| 封装 | 隐藏实现细节,暴露安全接口 | __属性名(名称改写为 _类名__属性名) |
| 继承 | 子类复用父类的代码 | class 子类(父类):super()、MRO |
| 多态 | 不同对象对同一方法做出不同响应 | 方法重写 + 鸭子类型 |
下一篇文章,我们将进入面向对象的进阶话题——魔术方法、__str__/__repr__、运算符重载、上下文管理器——这些是让你写出 Pythonic 代码的关键。

本文是「Python从入门到数据分析」系列的第 10 篇,共 24 篇。关注我,不错过后续更新。

posted @ 2026-05-31 10:58  AI视界尼奥  阅读(8)  评论(0)    收藏  举报