第7章:面向对象编程高级特性 - 让对象拥有"超能力"
学习目标:掌握Python面向对象的高级特性,包括魔术方法、属性管理、描述符、抽象基类和设计模式,能够设计出功能强大、扩展性好的类体系。
想象一下,如果你的对象可以像内置类型一样支持 +、-、== 等运算符,可以自动验证 属性值,可以像容器一样被迭代和索引,那该有多酷?这就是Python面向对象高级特性的魅力——让你的自定义类拥有"超能力"!
🎭 第7.1节:魔术方法与运算符重载 - 给对象添加"魔法"
什么是魔术方法?
魔术方法(Magic Methods),也叫特殊方法或双下划线方法,是Python中以双下划线开头和结尾的特殊方法。它们定义了对象的行为,让你的自定义类可以像内置类型一样工作。
就像给超级英雄赋予超能力一样,魔术方法给普通对象赋予了特殊能力:
# 没有魔术方法的普通类class SimpleVector:def __init__(self, x, y):self.x = xself.y = y# 使用时很不方便v1 = SimpleVector(1, 2)v2 = SimpleVector(3, 4)print(v1) # 输出:<__main__.SimpleVector object at 0x...> 看不懂!# v3 = v1 + v2 # 报错!不支持加法# 有魔术方法的"超能力"类class MagicVector:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return f"Vector({self.x}, {self.y})"def __add__(self, other):return MagicVector(self.x + other.x, self.y + other.y)# 使用起来就像内置类型一样自然v1 = MagicVector(1, 2)v2 = MagicVector(3, 4)print(v1) # 输出:Vector(1, 2) 清楚明了!v3 = v1 + v2 # 可以直接相加!print(v3) # 输出:Vector(4, 6)
对象生命周期魔术方法
1. __new__ vs __init__:对象的"出生"过程
很多人以为 __init__ 是构造函数,其实不然。对象的创建分为两步:
class Person:def __new__(cls, name, age):print(f"Step 1: __new__ 正在创建 {name} 的实例")# __new__ 负责创建实例instance = super().__new__(cls)return instancedef __init__(self, name, age):print(f"Step 2: __init__ 正在初始化 {name}")# __init__ 负责初始化实例self.name = nameself.age = age# 创建对象时会先调用 __new__ 再调用 __init__person = Person("张三", 25)# 输出:# Step 1: __new__ 正在创建 张三 的实例# Step 2: __init__ 正在初始化 张三
__new__ 的实际应用 - 单例模式:
class DatabaseConnection:_instance = Nonedef __new__(cls):# 单例模式:只创建一个实例if cls._instance is None:print("创建数据库连接...")cls._instance = super().__new__(cls)else:print("使用现有数据库连接...")return cls._instancedef __init__(self):if not hasattr(self, 'initialized'):self.host = "localhost"self.port = 3306self.initialized = True# 测试单例效果db1 = DatabaseConnection() # 创建数据库连接...db2 = DatabaseConnection() # 使用现有数据库连接...print(db1 is db2) # True,是同一个实例
2. __del__:对象的"葬礼"
class FileManager:def __init__(self, filename):self.filename = filenameself.file = open(filename, 'w')print(f"打开文件 {filename}")def __del__(self):# 对象被销毁时自动调用if hasattr(self, 'file') and not self.file.closed:self.file.close()print(f"自动关闭文件 {self.filename}")# 测试自动资源管理manager = FileManager("test.txt")manager = None # 删除引用,触发 __del__# 输出:自动关闭文件 test.txt
字符串表示魔术方法
__str__ vs __repr__:两种"自我介绍"方式
class Student:def __init__(self, name, grade, student_id):self.name = nameself.grade = gradeself.student_id = student_iddef __str__(self):# 给用户看的友好表示return f"{self.name}同学({self.grade}年级)"def __repr__(self):# 给开发者看的精确表示,最好能重现对象return f"Student('{self.name}', {self.grade}, '{self.student_id}')"student = Student("李雷", 3, "2021001")print(str(student)) # 李雷同学(3年级)print(repr(student)) # Student('李雷', 3, '2021001')# 在列表中显示时使用 __repr__students = [student]print(students) # [Student('李雷', 3, '2021001')]
最佳实践:
__str__:写给用户看的,要友好易读__repr__:写给开发者看的,要精确明确,最好能用来重建对象
__format__:自定义格式化
class Money:def __init__(self, amount):self.amount = amountdef __format__(self, format_spec):if format_spec == 'cn':return f"¥{self.amount:.2f}"elif format_spec == 'us':return f"${self.amount:.2f}"elif format_spec == 'int':return f"{int(self.amount)}"else:return f"{self.amount:.2f}"money = Money(1234.567)print(f"中文格式:{money:cn}") # 中文格式:¥1234.57print(f"美式格式:{money:us}") # 美式格式:$1234.57print(f"整数格式:{money:int}") # 整数格式:1234
比较运算符重载
class Grade:def __init__(self, score):self.score = scoredef __eq__(self, other):"""相等比较 =="""return self.score == other.scoredef __lt__(self, other):"""小于比较 <"""return self.score < other.scoredef __le__(self, other):"""小于等于 <="""return self.score <= other.scoredef __gt__(self, other):"""大于比较 >"""return self.score > other.scoredef __ge__(self, other):"""大于等于 >="""return self.score >= other.scoredef __ne__(self, other):"""不等于 !="""return self.score != other.scoredef __str__(self):return f"Grade({self.score})"# 测试比较操作grade1 = Grade(85)grade2 = Grade(92)grade3 = Grade(85)print(f"{grade1} == {grade3}: {grade1 == grade3}") # Trueprint(f"{grade1} < {grade2}: {grade1 < grade2}") # Trueprint(f"{grade1} > {grade2}: {grade1 > grade2}") # False# 可以排序了!grades = [Grade(78), Grade(92), Grade(85), Grade(67)]sorted_grades = sorted(grades)print("排序后的成绩:", [str(g) for g in sorted_grades])# 输出:排序后的成绩: ['Grade(67)', 'Grade(78)', 'Grade(85)', 'Grade(92)']```**技巧**:Python 可以从 `__eq__` 和 `__lt__` 自动推导其他比较方法,使用 `functools.total_ordering` 装饰器:
from functools import total_ordering@total_orderingclass SimpleGrade:def __init__(self, score):self.score = scoredef __eq__(self, other):return self.score == other.scoredef __lt__(self, other):return self.score < other.scoredef __str__(self):return f"Grade({self.score})"# 只定义了 __eq__ 和 __lt__,但其他比较方法也能工作!grade1 = SimpleGrade(85)grade2 = SimpleGrade(92)print(grade1 <= grade2) # True,自动推导出来的
算术运算符重载
让我们创建一个功能完整的向量类:
class Vector2D:def __init__(self, x, y):self.x = xself.y = y# 基本算术运算def __add__(self, other):"""向量加法"""return Vector2D(self.x + other.x, self.y + other.y)def __sub__(self, other):"""向量减法"""return Vector2D(self.x - other.x, self.y - other.y)def __mul__(self, scalar):"""向量数乘"""if isinstance(scalar, (int, float)):return Vector2D(self.x * scalar, self.y * scalar)else:raise TypeError("向量只能与数字相乘")def __rmul__(self, scalar):"""反向乘法:3 * vector"""return self.__mul__(scalar)def __truediv__(self, scalar):"""向量除法"""if isinstance(scalar, (int, float)) and scalar != 0:return Vector2D(self.x / scalar, self.y / scalar)else:raise ValueError("除数必须是非零数字")# 增强赋值运算符def __iadd__(self, other):"""+="""self.x += other.xself.y += other.yreturn selfdef __isub__(self, other):"""-="""self.x -= other.xself.y -= other.yreturn self# 一元运算符def __neg__(self):"""负号 -vector"""return Vector2D(-self.x, -self.y)def __abs__(self):"""绝对值(向量长度)"""return (self.x ** 2 + self.y ** 2) ** 0.5# 比较运算(按长度比较)def __eq__(self, other):return abs(self) == abs(other)def __lt__(self, other):return abs(self) < abs(other)def __str__(self):return f"Vector2D({self.x}, {self.y})"def __repr__(self):return f"Vector2D(x={self.x}, y={self.y})"# 测试完整的向量运算v1 = Vector2D(3, 4)v2 = Vector2D(1, 2)print(f"v1 = {v1}") # v1 = Vector2D(3, 4)print(f"v2 = {v2}") # v2 = Vector2D(1, 2)print(f"v1 + v2 = {v1 + v2}") # v1 + v2 = Vector2D(4, 6)print(f"v1 - v2 = {v1 - v2}") # v1 - v2 = Vector2D(2, 2)print(f"v1 * 2 = {v1 * 2}") # v1 * 2 = Vector2D(6, 8)print(f"3 * v1 = {3 * v1}") # 3 * v1 = Vector2D(9, 12)print(f"-v1 = {-v1}") # -v1 = Vector2D(-3, -4)print(f"|v1| = {abs(v1)}") # |v1| = 5.0# 增强赋值v1 += v2print(f"v1 += v2 后: {v1}") # v1 += v2 后: Vector2D(4, 6)
容器模拟魔术方法
让我们创建一个自定义的成绩册类,支持像列表一样的操作:
class GradeBook:def __init__(self):self._grades = {} # {学生姓名: 成绩}def __len__(self):"""支持 len() 函数"""return len(self._grades)def __getitem__(self, name):"""支持 [] 取值"""if name in self._grades:return self._grades[name]else:raise KeyError(f"学生 {name} 不存在")def __setitem__(self, name, grade):"""支持 [] 赋值"""if not isinstance(grade, (int, float)) or not 0 <= grade <= 100:raise ValueError("成绩必须是 0-100 之间的数字")self._grades[name] = gradedef __delitem__(self, name):"""支持 del 删除"""if name in self._grades:del self._grades[name]else:raise KeyError(f"学生 {name} 不存在")def __contains__(self, name):"""支持 in 操作符"""return name in self._gradesdef __iter__(self):"""支持 for 循环遍历"""return iter(self._grades.items())def __str__(self):return f"GradeBook({len(self)} students)"def __repr__(self):return f"GradeBook({dict(self._grades)})"# 测试容器功能gradebook = GradeBook()# 添加成绩(支持 [] 赋值)gradebook["张三"] = 85gradebook["李四"] = 92gradebook["王五"] = 78# 获取成绩(支持 [] 取值)print(f"