第 7 课我们掌握了面向对象编程的基础核心:类与对象、构造方法、封装、继承与多态,能够用 OOP 思想抽象现实事物并实现基础功能。但在复杂开发场景中,基础 OOP 特性仍存在局限:
- 方法类型单一:仅靠实例方法无法满足类级别操作(如统计所有对象数量)、独立工具函数(如数据校验)的需求;
- 属性访问繁琐 :封装中通过
get_xxx()/set_xxx()方法访问私有属性,语法不够优雅; - 子类行为不可控:无法强制子类实现特定方法,导致多态场景下子类行为不统一;
- 对象交互不灵活 :自定义对象无法支持 Python 内置操作(如
print()、len()、+运算)。
本节课将学习面向对象进阶特性:类方法、静态方法、属性装饰器、抽象类与接口、常用魔法方法。这些特性能解决基础 OOP 的局限,让你的代码更优雅、更规范、更具 Python 风格,是开发企业级应用的必备技能。
一、课程目标
✅ 掌握 类方法 (@classmethod)与 静态方法 (@staticmethod)的定义与使用,理解与实例方法的区别;✅ 精通 属性装饰器 (@property),实现私有属性的优雅访问与修改;✅ 学会使用 抽象类 (abc模块),强制子类实现指定方法,规范子类行为;✅ 熟悉常用 魔法方法 (如__str__、__repr__、__len__、__add__),让自定义对象支持 Python 内置操作;✅ 理解 组合 与 继承 的适用场景,学会用组合实现更灵活的功能扩展;✅ 能综合运用进阶 OOP 特性,编写规范、优雅的复杂程序(如图书管理系统、电商商品系统);✅ 建立 "Pythonic OOP" 思维,学会用 Python 特有的语法糖优化面向对象代码。
二、复习回顾:基础 OOP 的局限与进阶需求
我们以第 7 课的Student类为例,回顾基础 OOP 的局限:
python
运行
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有属性
# 基础封装:get/set方法访问私有属性(语法繁琐)
def get_age(self):
return self.__age
def set_age(self, age):
if age > 0:
self.__age = age
else:
print("年龄必须为正数!")
# 问题1:统计所有学生数量(需要类级别操作,基础OOP无法优雅实现)
# 问题2:访问私有属性需要调用get_age(),不如直接访问属性优雅
stu = Student("张三", 20)
print(stu.get_age()) # 繁琐,希望能直接print(stu.age)
# 问题3:无法强制子类实现特定方法,若有子类可能遗漏关键功能
本节课的进阶特性将逐一解决这些问题:用类方法统计学生数量,用@property实现优雅的属性访问,用抽象类强制子类实现方法。
三、核心知识点 1:类方法与静态方法 ------ 扩展方法类型
基础 OOP 中只有实例方法 (第一个参数为self,操作实例属性),进阶 OOP 提供了类方法 和静态方法,满足不同场景的需求。
3.1 类方法(@classmethod)------ 操作类属性的方法
3.1.1 定义与特点
- 装饰器:
@classmethod - 第一个参数:
cls(代表类本身,而非实例,Python 自动传入) - 功能:操作类属性,实现类级别的功能(如统计实例数量、创建实例的工厂方法)
- 调用方式:
类名.方法名()或对象名.方法名()(推荐用类名调用)
3.1.2 示例 1:用类方法统计实例数量
python
运行
class Student:
# 类属性:统计所有学生实例的数量
count = 0
def __init__(self, name, age):
self.name = name
self.age = age
# 每次创建实例,类属性count+1
Student.count += 1
# 类方法:获取学生总数(操作类属性count)
@classmethod
def get_total_count(cls):
# cls代表Student类,等价于Student.count
return f"当前学生总数:{cls.count}人"
# 创建实例
stu1 = Student("张三", 20)
stu2 = Student("李四", 21)
stu3 = Student("王五", 19)
# 调用类方法(推荐用类名调用)
print(Student.get_total_count()) # 输出:当前学生总数:3人
# 也可以用对象调用(不推荐)
print(stu1.get_total_count()) # 输出:当前学生总数:3人
3.1.3 示例 2:类方法作为工厂方法(创建实例的便捷方式)
工厂方法用于根据不同参数创建实例,简化实例化过程。比如,从字符串(如 "张三,20")创建Student实例:
python
运行
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# 工厂方法:从字符串创建实例
@classmethod
def from_string(cls, s):
# 拆分字符串为姓名和年龄
name, age = s.split(",")
# cls()等价于Student(),创建实例
return cls(name, int(age))
# 普通方式创建实例
stu1 = Student("张三", 20)
# 工厂方法创建实例(更便捷)
stu2 = Student.from_string("李四,21")
print(stu2.name, stu2.age) # 输出:李四 21
3.2 静态方法(@staticmethod)------ 独立的工具方法
3.2.1 定义与特点
- 装饰器:
@staticmethod - 无默认参数:既没有
self(实例),也没有cls(类) - 功能:实现与类和实例无关的独立工具功能(如数据校验、格式转换)
- 调用方式:
类名.方法名()或对象名.方法名()(推荐用类名调用)
3.2.2 示例 3:用静态方法实现数据校验
python
运行
class Student:
def __init__(self, name, age):
# 调用静态方法校验年龄
if Student.is_valid_age(age):
self.name = name
self.age = age
else:
raise ValueError("年龄必须为1-100之间的整数")
# 静态方法:校验年龄是否合法(独立工具功能)
@staticmethod
def is_valid_age(age):
return isinstance(age, int) and 1 <= age <= 100
# 调用静态方法(工具功能,可直接调用,无需创建实例)
print(Student.is_valid_age(20)) # 输出:True
print(Student.is_valid_age(150)) # 输出:False
# 创建实例(自动调用静态方法校验)
stu1 = Student("张三", 20)
# stu2 = Student("李四", 150) # 抛出ValueError
3.3 实例方法、类方法、静态方法的对比
| 方法类型 | 装饰器 | 第一个参数 | 操作对象 | 调用方式 | 适用场景 |
|---|---|---|---|---|---|
| 实例方法 | 无 | self | 实例属性 | 对象名。方法名 () | 操作实例的属性和行为 |
| 类方法 | @classmethod | cls | 类属性 | 类名。方法名 ()/ 对象名。方法名 () | 类级别操作、工厂方法 |
| 静态方法 | @staticmethod | 无 | 无(独立功能) | 类名。方法名 ()/ 对象名。方法名 () | 工具函数、数据校验、格式转换 |
四、核心知识点 2:属性装饰器 @property------ 优雅的属性访问
第 7 课中,我们通过get_xxx()/set_xxx()方法访问私有属性,语法繁琐。Python 提供了 **@property装饰器 **,可以将方法 "伪装" 成属性,实现优雅的只读访问 ,同时配合@属性名.setter实现可控的修改。
4.1 基础用法:@property 实现只读属性
语法:
python
运行
class 类名:
def __init__(self):
self.__私有属性 = 值
@property
def 公开属性名(self):
# 读取私有属性的逻辑
return self.__私有属性
示例 4:只读属性(获取学生年龄)
python
运行
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有属性
# @property:将get_age()方法伪装成age属性
@property
def age(self):
return self.__age
# 创建实例
stu = Student("张三", 20)
# 访问age属性(无需调用方法,直接访问)
print(stu.age) # 输出:20
# 尝试修改age属性(只读,无法修改,会报错)
# stu.age = 21 # 抛出AttributeError
4.2 进阶用法:@属性名.setter 实现可修改属性
语法:
python
运行
class 类名:
def __init__(self):
self.__私有属性 = 值
@property
def 公开属性名(self):
return self.__私有属性
@公开属性名.setter
def 公开属性名(self, value):
# 修改私有属性的逻辑(可添加校验)
if 校验条件:
self.__私有属性 = value
else:
抛出异常或给出提示
示例 5:可修改的年龄属性(带校验)
python
运行
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有属性
# 读取属性:@property
@property
def age(self):
return self.__age
# 修改属性:@age.setter(必须与@property的方法名相同)
@age.setter
def age(self, value):
if isinstance(value, int) and 1 <= value <= 100:
self.__age = value
else:
raise ValueError("年龄必须为1-100之间的整数")
# 创建实例
stu = Student("张三", 20)
# 访问属性
print(stu.age) # 输出:20
# 修改属性(优雅,且带校验)
stu.age = 21
print(stu.age) # 输出:21
# 尝试修改为非法值(抛出异常)
# stu.age = 150 # 抛出ValueError
4.3 高级用法:@属性名.deleter 实现属性删除
语法:
python
运行
@公开属性名.deleter
def 公开属性名(self):
# 删除私有属性的逻辑
del self.__私有属性
示例 6:可删除的属性
python
运行
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if 1 <= value <= 100:
self.__age = value
@age.deleter
def age(self):
del self.__age
print("年龄属性已删除")
stu = Student("张三", 20)
print(stu.age) # 输出:20
# 删除属性
del stu.age # 输出:年龄属性已删除
# 尝试访问已删除的属性(报错)
# print(stu.age) # 抛出AttributeError
五、核心知识点 3:抽象类与接口 ------ 规范子类行为
在继承场景中,我们可能需要强制子类实现特定方法 ,以保证多态的一致性。Python 中没有原生的 "接口" 概念,但可以通过 **abc模块 **(Abstract Base Classes)实现抽象类,强制子类实现抽象方法。
5.1 抽象类的定义与特点
- 导入:
from abc import ABC, abstractmethod - 抽象类:继承自
ABC的类,不能直接实例化(只能被继承) - 抽象方法:用
@abstractmethod装饰的方法,只有方法声明,没有方法实现,强制子类必须重写
5.2 示例 7:定义抽象类 Animal,强制子类实现 eat 方法
python
运行
from abc import ABC, abstractmethod
# 抽象类:Animal(继承自ABC)
class Animal(ABC):
def __init__(self, name):
self.name = name
# 抽象方法:eat(只有声明,没有实现)
@abstractmethod
def eat(self):
pass # 无需实现,子类必须重写
# 普通方法:非抽象方法,子类可继承
def sleep(self):
print(f"{self.name}在睡觉")
# 子类1:Dog(必须实现eat方法)
class Dog(Animal):
# 必须重写抽象方法eat
def eat(self):
print(f"{self.name}在吃骨头")
# 子类2:Cat(必须实现eat方法)
class Cat(Animal):
# 必须重写抽象方法eat
def eat(self):
print(f"{self.name}在吃鱼")
# 尝试实例化抽象类(报错,不能直接实例化)
# animal = Animal("动物") # 抛出TypeError
# 实例化子类(正常,因为已实现抽象方法)
dog = Dog("旺财")
cat = Cat("咪咪")
dog.eat() # 输出:旺财在吃骨头
cat.eat() # 输出:咪咪在吃鱼
dog.sleep() # 输出:旺财在睡觉(继承自抽象类的普通方法)
5.3 抽象类的作用
- 规范子类行为:强制子类实现指定方法,避免子类遗漏关键功能;
- 实现多态基础:保证所有子类都有相同的方法名,便于多态调用;
- 代码可读性:抽象类明确了子类的接口,开发者无需查看子类代码,即可知道子类必须实现的方法。
六、核心知识点 4:常用魔法方法 ------ 让对象支持 Python 内置操作
Python 中的魔法方法 (Magic Methods)是指以__开头和结尾的特殊方法(如__init__、__str__),这些方法会在特定场景下自动调用 ,让自定义对象支持 Python 的内置操作(如print()、len()、+运算)。
6.1 字符串表示:str__与__repr
__str__:用于print()、str()调用,返回用户友好的字符串表示;__repr__:用于交互式解释器、repr()调用,返回开发者友好的字符串表示(通常是对象的创建语句)。
示例 8:实现对象的字符串表示
python
运行
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# __str__:用户友好的表示
def __str__(self):
return f"Student(name='{self.name}', age={self.age})"
# __repr__:开发者友好的表示
def __repr__(self):
return f"Student('{self.name}', {self.age})"
stu = Student("张三", 20)
# print()调用__str__
print(stu) # 输出:Student(name='张三', age=20)
# 交互式解释器调用__repr__(如在IDLE中直接输入stu)
# >>> stu
# Student('张三', 20)
6.2 长度获取:len
- 用于
len()调用,返回对象的长度,让自定义对象支持len()操作。
示例 9:实现可获取长度的班级类
python
运行
class Class:
def __init__(self, name, students):
self.name = name
self.students = students # 学生列表
# __len__:支持len()调用
def __len__(self):
# 返回班级学生数量
return len(self.students)
cls = Class("三年级一班", ["张三", "李四", "王五"])
# 调用len(),自动执行__len__
print(len(cls)) # 输出:3
6.3 索引访问:getitem 、setitem 、delitem
__getitem__:用于对象[索引]读取,支持索引访问;__setitem__:用于对象[索引] = 值修改,支持索引赋值;__delitem__:用于del 对象[索引]删除,支持索引删除。
示例 10:实现支持索引的班级类
python
运行
class Class:
def __init__(self, name):
self.name = name
self.students = []
# 添加学生
def add_student(self, student):
self.students.append(student)
# __getitem__:读取索引
def __getitem__(self, index):
return self.students[index]
# __setitem__:修改索引
def __setitem__(self, index, value):
self.students[index] = value
# __delitem__:删除索引
def __delitem__(self, index):
del self.students[index]
cls = Class("三年级一班")
cls.add_student("张三")
cls.add_student("李四")
# 索引读取(调用__getitem__)
print(cls[0]) # 输出:张三
# 索引修改(调用__setitem__)
cls[1] = "王五"
print(cls[1]) # 输出:王五
# 索引删除(调用__delitem__)
del cls[0]
print(cls.students) # 输出:['王五']
6.4 运算符重载:add
- 用于
+运算,让自定义对象支持加法操作,实现对象的 "相加" 逻辑。
示例 11:实现学生成绩的加法运算
python
运行
class Score:
def __init__(self, chinese, math, english):
self.chinese = chinese
self.math = math
self.english = english
# __add__:支持+运算,实现两个成绩对象的相加
def __add__(self, other):
# 确保other是Score对象
if isinstance(other, Score):
return Score(
self.chinese + other.chinese,
self.math + other.math,
self.english + other.english
)
else:
raise TypeError("只能与Score对象相加")
def __str__(self):
return f"语文:{self.chinese},数学:{self.math},英语:{self.english}"
# 创建两个成绩对象
score1 = Score(85, 92, 78)
score2 = Score(90, 88, 95)
# 加法运算(调用__add__)
total_score = score1 + score2
print(total_score) # 输出:语文:175,数学:180,英语:173
6.5 常用魔法方法汇总
| 魔法方法 | 触发场景 | 功能 |
|---|---|---|
__init__ |
创建对象时 | 初始化对象属性 |
__str__ |
print()、str() |
用户友好的字符串表示 |
__repr__ |
交互式解释器、repr() |
开发者友好的字符串表示 |
__len__ |
len() |
获取对象长度 |
__getitem__ |
对象[索引] |
读取索引值 |
__setitem__ |
对象[索引] = 值 |
修改索引值 |
__delitem__ |
del 对象[索引] |
删除索引值 |
__add__ |
对象1 + 对象2 |
加法运算 |
__eq__ |
对象1 == 对象2 |
相等判断 |
__lt__ |
对象1 < 对象2 |
小于判断 |
七、核心知识点 5:组合与继承 ------ 灵活的功能扩展
在 OOP 中,扩展功能有两种方式:继承 和组合。
- 继承 :"是一个"(is-a)关系,如
Student是Person的子类; - 组合 :"有一个"(has-a)关系,如
Student有一个Score对象。
7.1 组合的实现与优势
组合通过在一个类中引用另一个类的对象实现功能扩展,相比继承,组合更灵活,避免了继承的层级过深和耦合过紧。
示例 12:用组合实现学生与成绩的关系
python
运行
# 成绩类(独立功能)
class Score:
def __init__(self, chinese, math, english):
self.chinese = chinese
self.math = math
self.english = english
def calc_avg(self):
return round((self.chinese + self.math + self.english) / 3, 1)
# 学生类(组合Score对象,实现功能扩展)
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score # 组合:Student有一个Score对象
def get_info(self):
return f"姓名:{self.name},年龄:{self.age},平均分:{self.score.calc_avg()}"
# 创建Score对象
score = Score(85, 92, 78)
# 创建Student对象(组合Score对象)
stu = Student("张三", 20, score)
print(stu.get_info()) # 输出:姓名:张三,年龄:20,平均分:85.0
7.2 继承与组合的选择原则
- 使用继承 :当子类与父类是 "is-a" 关系,且子类需要复用父类的大部分属性和方法时(如
GraduateStudent是Student的子类); - 使用组合 :当两个类是 "has-a" 关系,且需要灵活替换功能时(如
Student有Score,Score可以独立修改,不影响Student)。
核心原则 :优先使用组合,其次使用继承。组合能降低类之间的耦合度,让代码更灵活、更易维护。
八、综合实操案例:基于进阶 OOP 的图书管理系统
需求:
实现一个图书管理系统,支持以下功能:
- 图书类型:抽象类
Book,强制子类实现get_info()方法; - 具体图书:
PrintedBook(纸质书,含页数、价格)、Ebook(电子书,含文件大小、格式); - 图书管理:
BookManager类,支持添加图书、查询图书、统计图书总数(类方法)、校验 ISBN(静态方法); - 优雅访问:用
@property控制图书价格的修改(必须为正数); - 内置操作:用魔法方法实现
print()图书信息、len()统计图书数量。
实现代码:
python
运行
from abc import ABC, abstractmethod
# 抽象类:Book(强制子类实现get_info())
class Book(ABC):
def __init__(self, isbn, title, author):
self.isbn = isbn
self.title = title
self.author = author
@abstractmethod
def get_info(self):
"""返回图书信息(子类必须实现)"""
pass
# 纸质书类:PrintedBook(继承Book)
class PrintedBook(Book):
def __init__(self, isbn, title, author, pages, price):
super().__init__(isbn, title, author)
self.pages = pages
self.__price = price # 私有属性:价格
# @property:优雅访问价格
@property
def price(self):
return self.__price
# @price.setter:可控修改价格(必须为正数)
@price.setter
def price(self, value):
if value > 0:
self.__price = value
else:
raise ValueError("价格必须为正数")
# 实现抽象方法get_info()
def get_info(self):
return f"纸质书 - ISBN:{self.isbn},标题:{self.title},作者:{self.author},页数:{self.pages},价格:{self.price}元"
# __str__:支持print()
def __str__(self):
return self.get_info()
# 电子书类:Ebook(继承Book)
class Ebook(Book):
def __init__(self, isbn, title, author, file_size, format):
super().__init__(isbn, title, author)
self.file_size = file_size # 文件大小(MB)
self.format = format # 格式(epub、pdf)
# 实现抽象方法get_info()
def get_info(self):
return f"电子书 - ISBN:{self.isbn},标题:{self.title},作者:{self.author},大小:{self.file_size}MB,格式:{self.format}"
# __str__:支持print()
def __str__(self):
return self.get_info()
# 图书管理类:BookManager
class BookManager:
# 类属性:存储所有图书
books = []
# 静态方法:校验ISBN是否合法(简单校验:13位数字)
@staticmethod
def is_valid_isbn(isbn):
return isinstance(isbn, str) and len(isbn) == 13 and isbn.isdigit()
# 类方法:统计图书总数
@classmethod
def get_total_books(cls):
return len(cls.books)
# 实例方法:添加图书(支持多态,接收Book子类对象)
def add_book(self, book):
if isinstance(book, Book):
if BookManager.is_valid_isbn(book.isbn):
BookManager.books.append(book)
print(f"添加成功:{book.title}")
else:
print(f"添加失败:ISBN {book.isbn} 不合法")
else:
print("添加失败:不是有效的图书对象")
# 实例方法:查询所有图书
def query_all_books(self):
if not BookManager.books:
print("暂无图书信息")
return
print("\n=== 所有图书信息 ===")
for i, book in enumerate(BookManager.books, 1):
print(f"{i}. {book}") # 调用__str__
# __len__:支持len()统计图书数量
def __len__(self):
return BookManager.get_total_books()
# 系统运行入口
if __name__ == "__main__":
manager = BookManager()
# 添加纸质书
book1 = PrintedBook("9787111527312", "Python编程:从入门到实践", "埃里克·马瑟斯", 400, 89.0)
manager.add_book(book1)
# 添加电子书
book2 = Ebook("9787115546081", "Python进阶编程", "马特·哈里森", 50, "epub")
manager.add_book(book2)
# 尝试添加ISBN不合法的图书
book3 = PrintedBook("12345", "无效图书", "未知作者", 100, 20.0)
manager.add_book(book3)
# 查询所有图书
manager.query_all_books()
# 统计图书数量(类方法 + len())
print(f"\n图书总数(类方法):{BookManager.get_total_books()}")
print(f"图书总数(len()):{len(manager)}")
# 优雅修改纸质书价格
book1.price = 99.0
print(f"\n修改后价格:{book1.price}元")
print(book1)
运行结果:
plaintext
添加成功:Python编程:从入门到实践
添加成功:Python进阶编程
添加失败:ISBN 12345 不合法
=== 所有图书信息 ===
1. 纸质书 - ISBN:9787111527312,标题:Python编程:从入门到实践,作者:埃里克·马瑟斯,页数:400,价格:89.0元
2. 电子书 - ISBN:9787115546081,标题:Python进阶编程,作者:马特·哈里森,大小:50MB,格式:epub
图书总数(类方法):2
图书总数(len()):2
修改后价格:99.0元
纸质书 - ISBN:9787111527312,标题:Python编程:从入门到实践,作者:埃里克·马瑟斯,页数:400,价格:99.0元
九、课后作业(基础 + 进阶,巩固必做)
✅ 基础作业
- 定义
Circle类,包含类属性pi=3.14159,实例属性radius(半径),实现:- 类方法
from_diameter(cls, diameter):通过直径创建圆实例; - 静态方法
is_valid_radius(radius):校验半径是否为正数; - 实例方法
calc_area():计算圆的面积; @property:优雅访问半径,@radius.setter:校验半径修改。
- 类方法
- 定义抽象类
Shape,包含抽象方法calc_area()和calc_perimeter()(计算周长),实现子类Rectangle(矩形)和Circle(圆),强制实现抽象方法。 - 为
Student类实现__eq__方法,判断两个学生是否为同一人(姓名和年龄都相同),支持==运算。
✅ 进阶作业
- 完善图书管理系统,新增功能:删除图书、修改图书信息(用
@property确保修改合法)、按图书类型筛选(纸质书 / 电子书)。 - 用组合实现
Teacher类,包含name、age、courses(课程列表,每个课程是Course类的对象),实现课程的添加、删除、查询。 - 思考:继承和组合的核心区别是什么?在什么情况下应该优先使用组合?魔法方法的作用是什么?
十、后续学习指引
本节课我们掌握了 Python 面向对象的进阶特性:类方法、静态方法、属性装饰器、抽象类、魔法方法以及组合与继承的选择原则。这些特性让 OOP 代码更优雅、更规范、更灵活,能够解决复杂的开发场景。👉 下一课我们将学习 Python 模块与包 ------ 掌握如何组织大型项目的代码结构,实现模块的分层管理、包的创建与导入,以及第三方包的安装与使用,为开发大型项目打下基础。
十一、本节课核心总结
- 方法进阶 :类方法(
@classmethod,操作类属性,cls参数)、静态方法(@staticmethod,独立工具函数,无默认参数)与实例方法共同构成 Python 的方法体系,满足不同场景需求。 - 属性装饰器 :
@property实现私有属性的优雅只读访问,@属性名.setter实现可控修改,替代繁琐的get/set方法。 - 抽象类 :通过
abc模块实现,强制子类实现抽象方法,规范子类行为,是多态的重要保障。 - 魔法方法 :以
__开头结尾的特殊方法,自动触发,让自定义对象支持 Python 内置操作(如print()、len()、+),提升代码的 Python 风格。 - 组合与继承:优先使用组合(has-a 关系)实现功能扩展,降低耦合度;继承(is-a 关系)用于子类与父类的层级关系,复用代码。
- 核心思维 :进阶 OOP 的核心是优雅、规范、灵活,通过合理使用这些特性,让代码不仅能实现功能,还具备高可读性、高可维护性和高扩展性。