目录
1. 什么是魔法方法
1.1 魔法方法概念
魔法方法(Magic Methods),又称为双下划线方法或特殊方法,是 Python 中一种特殊的命名约定。这些方法名以双下划线开头和结尾,例如 __init__、__str__ 等。
核心特性:
- 自动调用:Python 解释器在特定情况下自动调用这些方法
- 约定优于配置:通过实现特定方法,让对象支持特定操作
- Pythonic 的关键:让自定义对象拥有内置类型一样的行为
想象你要开一家餐厅,你需要:
- (
__enter__/__exit__):管理营业时间,确保关门后打扫干净
基础示例:
classSimpleClass:def__init__(self, value): self.value = value print("对象已初始化")def__str__(self):returnf"SimpleClass(value={self.value})"def__repr__(self):returnf"SimpleClass({self.value!r})"
1.2 魔法方法的意义
没有魔法方法的局限性:
classPerson:def__init__(self, name, age): self.name = name self.age = agep = Person("Alice", 25)print(p) # 输出:<__main__.Person object at 0x...> - 不友好!p2 = Person("Alice", 25)print(p == p2) # False - 比较内存地址,不是内容!# len(p) # 报错:对象不支持len()操作
有了魔法方法的世界:
classPerson:def__init__(self, name, age): self.name = name self.age = agedef__str__(self):"""给用户看的友好表示"""returnf"{self.name}, {self.age}岁"def__repr__(self):"""给开发者看的精确表示"""returnf"Person(name={self.name!r}, age={self.age})"def__eq__(self, other):"""内容相等比较"""ifnot isinstance(other, Person):returnFalsereturn self.name == other.name and self.age == other.agedef__len__(self):"""假设长度是名字长度+年龄"""return len(self.name) + self.agep = Person("Alice", 25)print(p) # 输出:Alice, 25岁print(repr(p)) # 输出:Person(name='Alice', age=25)print(p == Person("Alice", 25)) # Trueprint(len(p)) # 30 (5 + 25)
提示:
- 魔法方法是 Python 中双下划线开头结尾的特殊方法
- 它们是 Python "协议"的具体实现,让代码更 Pythonic
2. 核心魔法方法介绍
2.1 对象生命周期方法
__new__ 方法
作用:创建对象实例(真正的构造函数)调用时机:在 __init__ 之前,创建对象时典型用途:单例模式、不可变对象、对象池、元类编程
classSingleton:"""单例模式实现""" _instance = Nonedef__new__(cls, *args, **kwargs): print("__new__ 被调用,创建实例")if cls._instance isNone:# 首次调用,创建新实例 cls._instance = super().__new__(cls)# 后续调用直接返回已有实例return cls._instancedef__init__(self, name): print("__init__ 被调用,初始化实例")# 注意:单例模式下__init__会被多次调用! self.name = name# 测试s1 = Singleton("第一")s2 = Singleton("第二")print(f"s1 is s2: {s1 is s2}") # Trueprint(f"s1.name: {s1.name}") # "第二"(注意:被覆盖了!)
提示:
__init__ 方法
作用:初始化对象实例调用时机:对象创建后立即调用最佳用法:参数验证、属性初始化、计算派生属性
classStudent:def__init__(self, name, score):# 参数验证ifnot isinstance(name, str):raise TypeError("姓名必须是字符串")ifnot0 <= score <= 100:raise ValueError("分数必须在0-100之间") self.name = name self.score = score# 计算派生属性 self.grade = self._calculate_grade() self.passed = score >= 60def_calculate_grade(self):"""根据分数计算等级"""if self.score >= 90:return"A"elif self.score >= 80:return"B"elif self.score >= 70:return"C"elif self.score >= 60:return"D"else:return"F"def__repr__(self):returnf"Student({self.name!r}, {self.score})"# 使用try: stu = Student("张三", 85) print(f"{stu.name}: 成绩{stu.grade}, {'及格'if stu.passed else'不及格'}")# 张三: 成绩B, 及格except ValueError as e: print(f"创建失败: {e}")
__del__ 方法
注意:Python 的垃圾回收机制可能不会立即调用 __del__
classResourceHolder:def__init__(self, name): self.name = name print(f"资源 {self.name} 已创建")def__del__(self):"""清理资源,但不要依赖它!""" print(f"资源 {self.name} 正在清理...")# 这里的打印可能永远不会执行!defclose(self):"""显式关闭方法,更可靠""" print(f"资源 {self.name} 已显式关闭")# 更好的替代:上下文管理器classSafeResource:def__init__(self, name): self.name = namedef__enter__(self): print(f"打开资源: {self.name}")return selfdef__exit__(self, exc_type, exc_val, exc_tb): print(f"关闭资源: {self.name}")if exc_type: print(f"发生异常: {exc_val}")returnFalse# 不抑制异常
2.2 字符串表示方法
__str__ vs __repr__ 详细对比
| __str__ | __repr__ |
|---|
| 目标用户 | | |
| 主要目的 | | |
| 调用方式 | str(obj) | repr(obj) |
| 实现原则 | | |
| 回退机制 | | |
classBook:def__init__(self, title, author, year): self.title = title self.author = author self.year = yeardef__str__(self):"""给用户看的:简洁友好的描述"""returnf"《{self.title}》 - {self.author} ({self.year})"def__repr__(self):"""给开发者看的:详细准确的表示"""# 注意:使用!r确保字符串被正确引用returnf"Book(title={self.title!r}, author={self.author!r}, year={self.year})"def__format__(self, format_spec):"""支持format()函数和f-string"""if format_spec == "short":returnf"{self.title[:10]}..."elif format_spec == "bib":returnf"{self.author} ({self.year}). {self.title}."else:return str(self)book = Book("Python编程从入门到实践", "Eric Matthes", 2016)print(str(book)) # 《Python编程从入门到实践》 - Eric Matthes (2016)print(repr(book)) # Book(title='Python编程从入门到实践', author='Eric Matthes', year=2016)print(f"{book:short}") # Python编程... print(f"{book:bib}") # Eric Matthes (2016). Python编程从入门到实践.# 在交互式环境中:# >>> book# Book(title='Python编程从入门到实践', author='Eric Matthes', year=2016)
提示:
repr(obj) 应该产生一个字符串,使得 eval(repr(obj)) == obj 成立(安全性允许的情况下)
2.3 比较运算符方法
为什么需要自定义比较?
默认情况下,Python 使用对象的内存地址进行比较:
classPoint:def__init__(self, x, y): self.x = x self.y = yp1 = Point(1, 2)p2 = Point(1, 2)print(p1 == p2) # False - 比较内存地址!print(p1 is p2) # False - 确实是不同对象
完整比较方法实现
import functools@functools.total_ordering # 装饰器自动补全其他比较方法classPoint:def__init__(self, x, y): self.x = x self.y = ydef__eq__(self, other):"""等于判断"""ifnot isinstance(other, Point):returnNotImplemented# 让Python尝试其他比较方式return self.x == other.x and self.y == other.ydef__lt__(self, other):"""小于判断(定义排序规则)"""ifnot isinstance(other, Point):returnNotImplemented# 先比较x,再比较yreturn (self.x, self.y) < (other.x, other.y)def__repr__(self):returnf"Point({self.x}, {self.y})"# 使用装饰器后,自动获得所有比较操作p1 = Point(1, 2)p2 = Point(1, 3)p3 = Point(1, 2)print(p1 == p3) # Trueprint(p1 != p2) # Trueprint(p1 < p2) # Trueprint(p1 <= p3) # Trueprint(p2 > p1) # Trueprint(p2 >= p1) # True# 排序points = [Point(3, 1), Point(1, 5), Point(1, 2), Point(2, 0)]points.sort()print(points) # [Point(1, 2), Point(1, 5), Point(2, 0), Point(3, 1)]
__hash__ 方法
提示:
- 如果定义了
__eq__,通常也需要定义 __hash__
classImmutablePoint:"""不可变点,可用作字典键""" __slots__ = ('_x', '_y') # 禁止动态添加属性def__init__(self, x, y): self._x = x self._y = y @propertydefx(self):return self._x @propertydefy(self):return self._ydef__eq__(self, other):ifnot isinstance(other, ImmutablePoint):returnFalsereturn self._x == other._x and self._y == other._ydef__hash__(self):# 使用元组计算哈希值return hash((self._x, self._y))def__repr__(self):returnf"ImmutablePoint({self._x}, {self._y})"# 可以作为字典键和集合元素p1 = ImmutablePoint(1, 2)p2 = ImmutablePoint(1, 2)p3 = ImmutablePoint(3, 4)# 字典使用cache = {p1: "点A", p3: "点B"}print(cache[p1]) # "点A"print(cache.get(p2)) # "点A" - p1和p2相等# 集合使用point_set = {p1, p2, p3}print(len(point_set)) # 2(p1和p2相同)
2.4 数值运算方法
基本算术运算示例
classVector:"""二维向量类"""def__init__(self, x, y): self.x = x self.y = y# 加法def__add__(self, other):"""向量加法"""ifnot isinstance(other, Vector):returnNotImplementedreturn Vector(self.x + other.x, self.y + other.y)# 减法def__sub__(self, other):ifnot isinstance(other, Vector):returnNotImplementedreturn Vector(self.x - other.x, self.y - other.y)# 标量乘法def__mul__(self, scalar):"""向量 * 标量"""ifnot isinstance(scalar, (int, float)):returnNotImplementedreturn Vector(self.x * scalar, self.y * scalar)# 反向乘法(标量 * 向量)def__rmul__(self, scalar):return self.__mul__(scalar)# 点积def__matmul__(self, other):"""向量点积 @ 操作符 (Python 3.5+)"""ifnot isinstance(other, Vector):returnNotImplementedreturn self.x * other.x + self.y * other.y# 字符串表示def__str__(self):returnf"Vector({self.x}, {self.y})"def__repr__(self):returnf"Vector({self.x}, {self.y})"# 绝对值(向量长度)def__abs__(self):return (self.x ** 2 + self.y ** 2) ** 0.5# 使用v1 = Vector(1, 2)v2 = Vector(3, 4)print(v1 + v2) # Vector(4, 6)print(v2 - v1) # Vector(2, 2)print(v1 * 3) # Vector(3, 6)print(2 * v2) # Vector(6, 8)print(v1 @ v2) # 11 (点积)print(abs(v1)) # 2.236 (向量长度)
完整数值运算方法参考表
| | | |
|---|
__add__ | + | __radd__ | |
__sub__ | - | __rsub__ | |
__mul__ | * | __rmul__ | |
__truediv__ | / | __rtruediv__ | |
__floordiv__ | // | __rfloordiv__ | |
__mod__ | % | __rmod__ | |
__pow__ | ** | __rpow__ | |
__matmul__ | @ | __rmatmul__ | |
__and__ | & | __rand__ | |
__or__ | | | __ror__ | |
__xor__ | ^ | __rxor__ | |
__lshift__ | << | __rlshift__ | |
__rshift__ | >> | __rrshift__ | |
__neg__ | - | | |
__pos__ | + | | |
__abs__ | abs() | | |
__invert__ | ~ | | |
__round__ | round() | | |
__floor__ | math.floor() | | |
__ceil__ | math.ceil() | | |
__trunc__ | math.trunc() | | |
__int__ | int() | | |
__float__ | float() | | |
__bool__ | bool() | | |
2.5 容器协议方法
让自定义对象像内置容器一样工作
classPlaylist:"""音乐播放列表,支持多种容器操作"""def__init__(self, name): self.name = name self.songs = [] self.current_index = 0defadd_song(self, song): self.songs.append(song)# 容器协议方法def__len__(self):"""支持 len() 函数"""return len(self.songs)def__getitem__(self, index):"""支持索引访问 playlist[index] 和切片 playlist[start:stop:step]"""if isinstance(index, slice):# 处理切片return self.songs[index]elif isinstance(index, int):# 处理整数索引,支持负数索引if index < 0: index = len(self.songs) + indexif0 <= index < len(self.songs):return self.songs[index]raise IndexError("播放列表索引超出范围")else:raise TypeError("索引必须是整数或切片")def__setitem__(self, index, song):"""支持索引赋值 playlist[index] = song""" self.songs[index] = songdef__delitem__(self, index):"""支持 del playlist[index]"""del self.songs[index]# 如果删除的是当前播放之前的歌曲,调整索引if index < self.current_index: self.current_index -= 1def__contains__(self, song):"""支持 in 操作符"""return song in self.songsdef__iter__(self):"""支持迭代(for song in playlist)""" self.current_index = 0# 重置迭代位置return selfdef__next__(self):"""迭代器协议"""if self.current_index < len(self.songs): song = self.songs[self.current_index] self.current_index += 1return songraise StopIterationdef__reversed__(self):"""支持 reversed() 函数"""return reversed(self.songs)defappend(self, song):"""模拟列表的 append 方法""" self.songs.append(song)definsert(self, index, song):"""模拟列表的 insert 方法""" self.songs.insert(index, song)if index <= self.current_index: self.current_index += 1def__str__(self):returnf"播放列表《{self.name}》共有{len(self)}首歌"def__repr__(self):returnf"Playlist(name={self.name!r}, songs={self.songs})"# 使用示例playlist = Playlist("我的最爱")playlist.add_song("歌曲A")playlist.append("歌曲B")playlist.insert(1, "歌曲C")print(len(playlist)) # 3print(playlist[0]) # "歌曲A"print(playlist[-1]) # "歌曲B"print(playlist[1:]) # ['歌曲C', '歌曲B']print("歌曲C"in playlist) # True# 迭代for song in playlist: print(f"播放: {song}")# 反向迭代for song in reversed(playlist): print(f"倒序播放: {song}")# 修改playlist[0] = "新歌曲A"del playlist[1]print(list(playlist)) # ['新歌曲A', '歌曲B']
2.6 可调用对象协议
__call__ 方法:让对象像函数一样工作
classCounter:"""可调用计数器,保持状态"""def__init__(self, start=0, step=1): self.value = start self.step = step self.history = []def__call__(self):"""每次调用增加计数""" self.history.append(self.value) self.value += self.stepreturn self.value - self.step # 返回增加前的值defreset(self):"""重置计数器""" self.value = 0 self.history.clear()def__str__(self):returnf"计数器: 当前值={self.value}, 步长={self.step}, 历史={self.history[-5:] if self.history else []}"def__repr__(self):returnf"Counter(start={self.value - len(self.history) * self.step}, step={self.step})"# 使用counter = Counter(start=10, step=2)print(counter()) # 10print(counter()) # 12print(counter()) # 14print(counter) # 计数器: 当前值=16, 步长=2, 历史=[10, 12, 14]# 实际应用:函数装饰器import timefrom functools import wrapsclassTimer:"""计时装饰器"""def__init__(self, name=""): self.name = name self.total_time = 0 self.call_count = 0def__call__(self, func): @wraps(func)defwrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start self.total_time += elapsed self.call_count += 1if self.name: print(f"[{self.name}] {func.__name__} 耗时: {elapsed:.6f}秒")else: print(f"{func.__name__} 耗时: {elapsed:.6f}秒")return resultreturn wrapper @propertydefaverage_time(self):"""平均执行时间"""return self.total_time / self.call_count if self.call_count > 0else0# 使用装饰器@Timer("Fibonacci")deffibonacci(n):if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)print(fibonacci(10)) # 55,并打印每次调用耗时
2.7 上下文管理协议
__enter__ 和 __exit__ 方法:资源安全管理
import sqlite3import threadingfrom contextlib import ContextDecoratorclassDatabaseTransaction:"""数据库事务上下文管理器"""def__init__(self, db_path): self.db_path = db_path self.connection = None self.cursor = Nonedef__enter__(self):"""进入上下文:建立连接""" self.connection = sqlite3.connect(self.db_path) self.cursor = self.connection.cursor() print(f"已连接到数据库: {self.db_path}")return self.cursordef__exit__(self, exc_type, exc_val, exc_tb):"""退出上下文:提交或回滚"""if exc_type isNone:# 没有异常,提交事务 self.connection.commit() print("事务已提交")else:# 有异常,回滚事务 self.connection.rollback() print(f"事务已回滚,原因: {exc_val}")# 清理资源if self.cursor: self.cursor.close()if self.connection: self.connection.close() print("数据库连接已关闭")# 返回False让异常继续传播returnFalseclassThreadSafeLock(ContextDecorator):"""线程锁上下文管理器,也可用作装饰器"""def__init__(self, lock=None): self.lock = lock or threading.Lock() self.lock_name = getattr(lock, 'name', '未命名锁')def__enter__(self): print(f"获取锁: {self.lock_name}") self.lock.acquire()return selfdef__exit__(self, exc_type, exc_val, exc_tb): print(f"释放锁: {self.lock_name}") self.lock.release()returnFalsedef__call__(self, func):"""支持作为装饰器使用"""defwrapper(*args, **kwargs):with self:return func(*args, **kwargs)return wrapper# 使用示例print("=== 数据库事务示例 ===")try:with DatabaseTransaction(":memory:") as cursor: cursor.execute("CREATE TABLE users (id INTEGER, name TEXT)") cursor.execute("INSERT INTO users VALUES (1, 'Alice')") cursor.execute("INSERT INTO users VALUES (2, 'Bob')")# 模拟异常# raise ValueError("测试异常") cursor.execute("SELECT * FROM users") print("查询结果:", cursor.fetchall())except Exception as e: print(f"捕获到异常: {e}")print("\n=== 线程锁示例 ===")shared_counter = 0lock = threading.Lock()@ThreadSafeLock(lock)defincrement_counter():global shared_counter# 这个函数会在锁的保护下执行 shared_counter += 1 print(f"计数器: {shared_counter}")# 多线程测试threads = []for i in range(5): t = threading.Thread(target=increment_counter) threads.append(t) t.start()for t in threads: t.join()print(f"最终计数器值: {shared_counter}")
提示:
- 对象生命周期方法:
__new__、__init__、__del__ - 字符串表示:
__str__(用户友好)、__repr__(开发者精确) - 比较运算:实现
__eq__ 和 __lt__,可使用 @total_ordering - 哈希运算:
__eq__ 和 __hash__ 必须配对实现 - 容器协议:让对象支持
len()、索引、迭代等操作 - 可调用对象:
__call__ 让实例像函数一样使用 - 上下文管理:
__enter__/__exit__ 实现资源自动管理
3. 何时使用魔法方法
3.1 判断标准:你真的需要魔法方法吗?
需要使用魔法方法的情况:
自定义对象显示
classProduct:def__init__(self, name, price): self.name = name self.price = price# 需要:让print(product)显示有用信息def__str__(self):returnf"{self.name}: ¥{self.price:.2f}"def__repr__(self):returnf"Product({self.name!r}, {self.price})"
自定义相等比较
classStudent:def__init__(self, student_id, name): self.student_id = student_id self.name = name# 需要:按学号比较学生是否相同def__eq__(self, other):return self.student_id == other.student_iddef__hash__(self):return hash(self.student_id)
创建数值类型
classComplexNumber:def__init__(self, real, imag): self.real = real self.imag = imag# 需要:支持复数运算def__add__(self, other):return ComplexNumber( self.real + other.real, self.imag + other.imag )
创建容器类
classShoppingCart:def__init__(self): self.items = [] self.quantities = {}# 需要:让购物车支持len()和迭代def__len__(self):return sum(self.quantities.values())def__iter__(self):return iter(self.items)
不需要使用魔法方法的情况:
简单的数据容器 → 使用 @dataclass
from dataclasses import dataclass, fieldfrom typing import List@dataclass(order=True) # 自动生成比较方法classPoint: x: float y: float tags: List[str] = field(default_factory=list)# 自动生成 __init__, __repr__, __eq__, __lt__ 等p1 = Point(1.0, 2.0)p2 = Point(1.0, 2.0)print(p1 == p2) # True
只读数据结构 → 使用 NamedTuple
from typing import NamedTupleclassCoordinate(NamedTuple): x: float y: float z: float = 0.0# 默认值# 自动实现 __eq__, __hash__, __repr__ 等coord = Coordinate(1.0, 2.0)print(coord.x) # 1.0print(coord) # Coordinate(x=1.0, y=2.0, z=0.0)
行为简单的类 → 使用普通类
# 不需要魔法方法的情况classConfig:"""简单的配置类,不需要特殊行为"""def__init__(self, **kwargs):for key, value in kwargs.items(): setattr(self, key, value)config = Config(host="localhost", port=8080)print(config.host) # localhost
3.2 应用建议
渐进式实现策略:
classSmartVector:"""智能向量类 - 渐进式实现示例"""# 第一步:基本初始化def__init__(self, x, y): self.x = x self.y = y# 第二步:调试表示(尽早实现)def__repr__(self):returnf"SmartVector({self.x}, {self.y})"# 第三步:用户友好显示(可选但推荐)def__str__(self):returnf"向量({self.x}, {self.y})"# 第四步:相等比较(如果需要)def__eq__(self, other):ifnot isinstance(other, SmartVector):returnNotImplementedreturn self.x == other.x and self.y == other.y# 第五步:哈希支持(如果需要作为字典键)def__hash__(self):return hash((self.x, self.y))# 第六步:其他功能(按需添加)def__add__(self, other):ifnot isinstance(other, SmartVector):returnNotImplementedreturn SmartVector(self.x + other.x, self.y + other.y)def__abs__(self):import mathreturn math.hypot(self.x, self.y)
错误处理与健壮性:
classSafeFraction:"""安全的分数类,包含完整错误处理"""def__init__(self, numerator, denominator):# 类型检查ifnot isinstance(numerator, (int, float)):raise TypeError(f"分子必须是数字,不是 {type(numerator).__name__}")ifnot isinstance(denominator, (int, float)):raise TypeError(f"分母必须是数字,不是 {type(denominator).__name__}")# 值检查if denominator == 0:raise ValueError("分母不能为零")# 规范化 self.numerator = numerator self.denominator = denominator self._normalize()def_normalize(self):"""化简分数"""import math gcd = math.gcd(int(self.numerator), int(self.denominator)) self.numerator //= gcd self.denominator //= gcd# 确保分母为正if self.denominator < 0: self.numerator = -self.numerator self.denominator = -self.denominatordef__add__(self, other):"""加法运算,包含类型检查"""ifnot isinstance(other, SafeFraction):# 尝试转换为分数try: other = SafeFraction(other, 1)except (TypeError, ValueError):returnNotImplemented# 让Python尝试其他方式 new_num = self.numerator * other.denominator + other.numerator * self.denominator new_den = self.denominator * other.denominatorreturn SafeFraction(new_num, new_den)def__str__(self):returnf"{self.numerator}/{self.denominator}"def__repr__(self):returnf"SafeFraction({self.numerator}, {self.denominator})"# 测试健壮性try: f1 = SafeFraction(1, 2) f2 = SafeFraction(3, 4) print(f1 + f2) # 5/4 print(f1 + 2) # 5/2# f3 = SafeFraction(1, 0) # ValueError: 分母不能为零# f4 = SafeFraction("a", 2) # TypeError: 分子必须是数字except Exception as e: print(f"错误: {e}")
一致性原则:
- 如果实现了
__eq__,也应该实现 __hash__ - 如果实现了
__lt__,考虑使用 @total_ordering - 容器类应该实现完整协议(
__len__, __getitem__, __iter__等)
from functools import total_ordering@total_orderingclassConsistentClass:"""保持一致的魔法方法实现"""def__init__(self, value): self.value = valuedef__eq__(self, other):ifnot isinstance(other, ConsistentClass):returnNotImplementedreturn self.value == other.valuedef__lt__(self, other):ifnot isinstance(other, ConsistentClass):returnNotImplementedreturn self.value < other.valuedef__hash__(self):return hash(self.value)def__repr__(self):returnf"ConsistentClass({self.value})"
提示:
- 简单数据类优先使用
@dataclass 或 NamedTuple
4. 应用示例
4.1 案例一:实现一个简单的分数类
import mathfrom functools import total_ordering@total_orderingclassFraction:"""完整的分数类实现,支持所有基本运算"""def__init__(self, numerator, denominator=1):""" 初始化分数 :param numerator: 分子 :param denominator: 分母(不能为0) """ifnot isinstance(numerator, (int, float)):raise TypeError("分子必须是数字")ifnot isinstance(denominator, (int, float)):raise TypeError("分母必须是数字")if denominator == 0:raise ValueError("分母不能为零")# 转换为整数if isinstance(numerator, float):# 处理浮点数:先找到合适的分母 numerator, denominator = self._float_to_fraction(numerator, denominator)# 化简分数 self._reduce(numerator, denominator)def_float_to_fraction(self, numerator, denominator, precision=1e-6):"""将浮点数转换为分数近似"""from fractions import Fraction as PyFraction frac = PyFraction(numerator / denominator).limit_denominator()return frac.numerator, frac.denominatordef_reduce(self, numerator, denominator):"""化简分数到最简形式"""# 使用math.gcd计算最大公约数 gcd_val = math.gcd(abs(int(numerator)), abs(int(denominator))) self.numerator = numerator // gcd_val self.denominator = denominator // gcd_val# 确保分母为正if self.denominator < 0: self.numerator = -self.numerator self.denominator = -self.denominator# 算术运算方法def__add__(self, other): other = self._ensure_fraction(other) new_num = self.numerator * other.denominator + other.numerator * self.denominator new_den = self.denominator * other.denominatorreturn Fraction(new_num, new_den)def__sub__(self, other): other = self._ensure_fraction(other) new_num = self.numerator * other.denominator - other.numerator * self.denominator new_den = self.denominator * other.denominatorreturn Fraction(new_num, new_den)def__mul__(self, other): other = self._ensure_fraction(other)return Fraction(self.numerator * other.numerator, self.denominator * other.denominator)def__truediv__(self, other): other = self._ensure_fraction(other)if other.numerator == 0:raise ZeroDivisionError("除以零")return Fraction(self.numerator * other.denominator, self.denominator * other.numerator)def__pow__(self, power):"""支持分数幂运算"""if isinstance(power, int):return Fraction(self.numerator ** power, self.denominator ** power)elif isinstance(power, float):# 返回浮点数结果return float(self) ** powerelse:returnNotImplemented# 比较运算方法def__eq__(self, other): other = self._ensure_fraction(other)return (self.numerator == other.numerator and self.denominator == other.denominator)def__lt__(self, other): other = self._ensure_fraction(other)return self.numerator * other.denominator < other.numerator * self.denominator# 类型转换方法def__float__(self):return self.numerator / self.denominatordef__int__(self):return self.numerator // self.denominatordef__bool__(self):return self.numerator != 0# 字符串表示方法def__str__(self):if self.denominator == 1:return str(self.numerator)returnf"{self.numerator}/{self.denominator}"def__repr__(self):returnf"Fraction({self.numerator}, {self.denominator})"def__format__(self, format_spec):"""支持格式化输出"""if format_spec == 'decimal':returnf"{float(self):.4f}"elif format_spec == 'mixed': whole = self.numerator // self.denominator remainder = abs(self.numerator) % self.denominatorif whole == 0:return str(self)elif remainder == 0:return str(whole)else:returnf"{whole}{remainder}/{self.denominator}"else:return str(self)# 辅助方法def_ensure_fraction(self, value):"""确保输入是Fraction类型"""if isinstance(value, (int, float)):return Fraction(value)elif isinstance(value, Fraction):return valueelse:raise TypeError(f"不支持的类型: {type(value)}")# 属性访问 @propertydefdecimal(self):"""获取小数形式"""return float(self) @propertydefreciprocal(self):"""获取倒数"""return Fraction(self.denominator, self.numerator)# 类方法 @classmethoddeffrom_float(cls, value, max_denominator=1000):"""从浮点数创建分数"""from fractions import Fraction as PyFraction frac = PyFraction(value).limit_denominator(max_denominator)return cls(frac.numerator, frac.denominator)# 使用示例print("=== 分数类使用示例 ===")f1 = Fraction(1, 2)f2 = Fraction(3, 4)print(f"f1 = {f1}, f2 = {f2}")print(f"f1 + f2 = {f1 + f2}") # 5/4print(f"f1 - f2 = {f1 - f2}") # -1/4print(f"f1 * f2 = {f1 * f2}") # 3/8print(f"f1 / f2 = {f1 / f2}") # 2/3print(f"f1 ** 2 = {f1 ** 2}") # 1/4print(f"f1 < f2: {f1 < f2}") # Trueprint(f"f1 的小数形式: {f1.decimal:.3f}") # 0.500print(f"f1 的倒数: {f1.reciprocal}") # 2/1print(f"格式化输出: {f2:mixed}") # 0 3/4print(f"从浮点数创建: {Fraction.from_float(0.333)}") # 333/1000# 混合类型运算print(f"f1 + 2 = {f1 + 2}") # 5/2print(f"3 * f2 = {3 * f2}") # 9/4 (使用__rmul__)
4.2 案例二:实现一个智能配置文件类
import jsonimport yaml # 需要安装: pip install pyyamlfrom pathlib import Pathfrom typing import Any, Dict, Optional, UnionclassConfig:""" 智能配置文件类 支持JSON/YAML格式,提供字典和属性两种访问方式 """def__init__(self, filepath: Union[str, Path] = "config.json", auto_save: bool = True, default_config: Optional[Dict] = None):""" 初始化配置管理器 :param filepath: 配置文件路径 :param auto_save: 是否自动保存修改 :param default_config: 默认配置 """ self.filepath = Path(filepath) self.auto_save = auto_save self._data = {}# 加载或初始化配置if self.filepath.exists(): self._load()elif default_config: self._data.update(default_config)if auto_save: self.save()# 跟踪修改 self._modified = Falsedef_detect_format(self) -> str:"""检测文件格式""" suffix = self.filepath.suffix.lower()if suffix in ['.json']:return'json'elif suffix in ['.yaml', '.yml']:return'yaml'else:# 默认使用JSONreturn'json'def_load(self):"""加载配置文件""" fmt = self._detect_format()try:with open(self.filepath, 'r', encoding='utf-8') as f:if fmt == 'json': self._data = json.load(f)elif fmt == 'yaml': self._data = yaml.safe_load(f)except (json.JSONDecodeError, yaml.YAMLError) as e: print(f"配置文件解析错误: {e}") self._data = {}except Exception as e: print(f"加载配置文件失败: {e}") self._data = {}defsave(self, filepath: Optional[Union[str, Path]] = None):"""保存配置文件""" save_path = Path(filepath) if filepath else self.filepath fmt = self._detect_format() if filepath isNoneelse self._detect_format_from_path(save_path)try:# 确保目录存在 save_path.parent.mkdir(parents=True, exist_ok=True)with open(save_path, 'w', encoding='utf-8') as f:if fmt == 'json': json.dump(self._data, f, indent=2, ensure_ascii=False)elif fmt == 'yaml': yaml.dump(self._data, f, default_flow_style=False, allow_unicode=True) self._modified = False print(f"配置已保存到: {save_path}")returnTrueexcept Exception as e: print(f"保存配置文件失败: {e}")returnFalsedef_detect_format_from_path(self, path: Path) -> str:"""从路径检测格式""" suffix = path.suffix.lower()return'yaml'if suffix in ['.yaml', '.yml'] else'json'# ============ 魔法方法实现 ============# 字典式访问def__getitem__(self, key: str) -> Any:"""支持 config['key'] 语法""" keys = key.split('.') value = self._datafor k in keys:if isinstance(value, dict) and k in value: value = value[k]else:raise KeyError(f"配置键 '{key}' 不存在")return valuedef__setitem__(self, key: str, value: Any):"""支持 config['key'] = value 语法,支持点号路径""" keys = key.split('.') data = self._data# 导航到目标字典(创建中间路径)for k in keys[:-1]:if k notin data ornot isinstance(data[k], dict): data[k] = {} data = data[k]# 设置值 data[keys[-1]] = value self._modified = Trueif self.auto_save: self.save()def__delitem__(self, key: str):"""支持 del config['key'] 语法""" keys = key.split('.') data = self._datafor k in keys[:-1]:if k notin data:raise KeyError(f"配置键 '{key}' 不存在") data = data[k]if keys[-1] in data:del data[keys[-1]] self._modified = Trueif self.auto_save: self.save()else:raise KeyError(f"配置键 '{key}' 不存在")def__contains__(self, key: str) -> bool:"""支持 'key' in config 语法"""try: _ = self[key]returnTrueexcept KeyError:returnFalsedefget(self, key: str, default: Any = None) -> Any:"""安全的获取方法"""try:return self[key]except KeyError:return default# 容器协议def__len__(self) -> int:"""支持 len(config)"""return len(self._data)def__iter__(self):"""支持迭代(返回顶层键)"""return iter(self._data)defkeys(self):"""返回所有键"""return self._data.keys()defvalues(self):"""返回所有值"""return self._data.values()defitems(self):"""返回所有键值对"""return self._data.items()# 属性式访问def__getattr__(self, name: str) -> Any:"""支持 config.key 语法"""if name in self._data: value = self._data[name]# 如果是字典,返回包装对象以支持链式属性访问if isinstance(value, dict):return DictProxy(value, parent=self, path=name)return valueraise AttributeError(f"配置项 '{name}' 不存在")def__setattr__(self, name: str, value: Any):"""支持 config.key = value 语法"""if name in ('filepath', '_data', 'auto_save', '_modified'):# 特殊属性 super().__setattr__(name, value)else: self[name] = valuedef__delattr__(self, name: str):"""支持 del config.key 语法"""if name in self._data:del self[name]else: super().__delattr__(name)# 字符串表示def__str__(self) -> str:returnf"Config({self.filepath.name}): {len(self)} 个配置项"def__repr__(self) -> str:returnf"Config(filepath={self.filepath!r}, auto_save={self.auto_save})"def__enter__(self):"""上下文管理器支持"""return selfdef__exit__(self, exc_type, exc_val, exc_tb):"""退出上下文时自动保存"""if self._modified: self.save()returnFalse# 实用方法defupdate(self, other: Dict):"""更新配置(类似字典的update)""" self._data.update(other) self._modified = Trueif self.auto_save: self.save()defclear(self):"""清空配置""" self._data.clear() self._modified = Trueif self.auto_save: self.save()defreload(self):"""重新加载配置文件"""if self.filepath.exists(): self._load() self._modified = False print(f"配置已重新加载")else: print(f"配置文件不存在: {self.filepath}") @propertydefmodified(self) -> bool:"""配置是否已被修改"""return self._modifiedclassDictProxy:"""字典代理,支持链式属性访问"""def__init__(self, data: Dict, parent=None, path=""): self._data = data self._parent = parent self._path = f"{path}."if path else""def__getattr__(self, name: str) -> Any:if name in self._data: value = self._data[name]if isinstance(value, dict):return DictProxy(value, self._parent, f"{self._path}{name}")return valueraise AttributeError(f"键 '{name}' 不存在")def__getitem__(self, key: str) -> Any:return self._data[key]def__repr__(self) -> str:returnf"DictProxy({self._data})"# 使用示例print("\n=== 智能配置类使用示例 ===")# 创建配置管理器config = Config("my_config.json", auto_save=True)# 多种方式设置配置config['app.name'] = "MyApp"# 字典式,支持点号路径config['app.version'] = "1.0.0"config['database.host'] = "localhost"config['database.port'] = 5432config['features'] = {"dark_mode": True, "notifications": False}# 属性式访问config.theme = "dark"config.language = "zh-CN"print(f"应用名称: {config['app.name']}") # MyAppprint(f"数据库主机: {config.database.host}") # localhost (链式属性访问!)print(f"主题: {config.theme}") # dark# 检查存在性print(f"'app.name' in config: {'app.name'in config}") # Trueprint(f"'missing.key' in config: {'missing.key'in config}") # False# 安全获取print(f"获取不存在的键: {config.get('missing.key', '默认值')}") # 默认值# 迭代配置print("\n所有配置项:")for key in config: print(f" {key}: {config[key]}")# 删除配置del config['database.port']print(f"删除后数据库端口: {config.get('database.port', '未设置')}") # 未设置# 上下文管理器with Config("temp_config.yaml") as temp_config: temp_config['temp.value'] = "临时值" temp_config['another.value'] = 123# 退出上下文时自动保存# 重新加载config.reload()print(f"\n配置项数量: {len(config)}")# 导出为字典print(f"原始数据: {config._data}")
5. 常见问题
5.1 常见陷阱
陷阱1:__init__ 不是真正的构造函数
classMisunderstandingInit:def__init__(self): print("__init__ 被调用")# 错误尝试:__init__ 不能返回非None值# return "something" # TypeError: __init__() should return None @classmethoddefcreate(cls):"""正确的创建方法""" instance = cls.__new__(cls)if instance isnotNone: instance.__init__()return instanceclassCorrectApproach:def__new__(cls, create_special=False): print("__new__ 被调用 - 真正的构造函数")if create_special:# __new__可以返回不同类型的对象return"这是一个字符串,不是CorrectApproach实例"return super().__new__(cls)def__init__(self): print("__init__ 被调用 - 初始化已创建的对象")# 测试obj1 = CorrectApproach() # 正常创建obj2 = CorrectApproach(create_special=True) # 返回字符串print(f"obj2类型: {type(obj2)}, 值: {obj2}")
陷阱2:__eq__ 和 __hash__ 必须配对实现
classUnhashableError:"""错误的类:有__eq__但没有__hash__"""def__init__(self, value): self.value = valuedef__eq__(self, other):return self.value == other.value# 缺少 __hash__ 方法!# Python 3中这会使得对象不可哈希classCorrectHashable:"""正确的类:__eq__和__hash__配对"""def__init__(self, value): self.value = valuedef__eq__(self, other):return self.value == other.valuedef__hash__(self):return hash(self.value)def__repr__(self):returnf"CorrectHashable({self.value})"# 测试try: bad1 = UnhashableError(1) bad2 = UnhashableError(1)# hash(bad1) # 报错: TypeError: unhashable type: 'UnhashableError'# {bad1, bad2} # 报错 print("UnhashableError 对象不可哈希")except TypeError as e: print(f"错误: {e}")# 正确的使用good1 = CorrectHashable(1)good2 = CorrectHashable(1)good3 = CorrectHashable(2)print(f"good1 == good2: {good1 == good2}") # Trueprint(f"hash(good1) == hash(good2): {hash(good1) == hash(good2)}") # True# 可以作为字典键和集合元素dict_with_hashable = {good1: "值1", good3: "值3"}set_with_hashable = {good1, good2, good3} # 只有good1和good3,good2与good1相等print(f"字典: {dict_with_hashable}")print(f"集合大小: {len(set_with_hashable)}") # 2
陷阱3:无限递归调用
classInfiniteRecursion:"""危险的__getattribute__实现"""def__init__(self): self.value = 42def__getattribute__(self, name):# 错误:会导致无限递归!# return self.name # 调用self.name又会触发__getattribute__# 正确:使用object.__getattribute__return object.__getattribute__(self, name)defsafe_get_attribute(self, name):"""安全的属性获取方法"""return object.__getattribute__(self, name)classSafeClass:"""安全的属性访问"""def__init__(self): self._data = {"x": 1, "y": 2}def__getattr__(self, name):"""只有在属性不存在时才调用"""if name in self._data:return self._data[name]raise AttributeError(f"属性 '{name}' 不存在")def__setattr__(self, name, value):"""属性设置"""if name == '_data': super().__setattr__(name, value)else: self._data[name] = value# 测试安全类safe = SafeClass()print(f"safe.x: {safe.x}") # 1print(f"safe.y: {safe.y}") # 2safe.z = 3print(f"safe.z: {safe.z}") # 3
陷阱4:修改不可变对象
classImmutablePoint:"""设计为不可变对象""" __slots__ = ('_x', '_y') # 限制属性def__init__(self, x, y): super().__setattr__('_x', x) super().__setattr__('_y', y) @propertydefx(self):return self._x @propertydefy(self):return self._ydef__setattr__(self, name, value):"""禁止修改属性"""raise AttributeError(f"{self.__class__.__name__} 对象是只读的")def__eq__(self, other):return self._x == other._x and self._y == other._ydef__hash__(self):return hash((self._x, self._y))def__repr__(self):returnf"ImmutablePoint({self._x}, {self._y})"# 测试不可变性point = ImmutablePoint(1, 2)print(f"点坐标: ({point.x}, {point.y})")try: point.x = 3# 报错except AttributeError as e: print(f"预期错误: {e}")# 可以作为字典键points_map = {point: "点A"}print(f"点是否在字典中: {point in points_map}") # True
5.2 调试技巧
技巧1:使用 __dict__ 查看对象内部
classDebuggableClass:def__init__(self, name, value, **kwargs): self.name = name self.value = value# 动态添加额外属性for key, val in kwargs.items(): setattr(self, key, val)# 私有属性 self._secret = "隐藏数据" self.__very_private = "非常私有"definspect(self):"""查看对象内部状态""" print(f"对象ID: {id(self)}") print(f"类型: {type(self).__name__}") print(f"模块: {self.__module__}") print(f"类名: {self.__class__.__name__}") print("\n所有属性 (通过__dict__):")for key, value in self.__dict__.items(): print(f" {key}: {value!r}") print("\n所有属性 (通过dir):") attrs = [attr for attr in dir(self) ifnot attr.startswith('__') ornot attr.endswith('__')] print(f" {attrs[:10]}...") # 只显示前10个# 创建并检查对象obj = DebuggableClass("测试对象", 100, extra1="额外1", extra2=3.14)obj.inspect()# 直接访问__dict__print(f"\n直接访问__dict__: {obj.__dict__}")# 获取私有属性名(名称修饰)print(f"\n名称修饰后的私有属性: {obj._DebuggableClass__very_private}")
技巧2:检查魔法方法实现
defanalyze_magic_methods(obj):"""分析对象的魔法方法实现情况"""import inspect print(f"分析对象: {obj}") print(f"类型: {type(obj).__name__}")# 获取所有魔法方法 all_magic_methods = ['__init__', '__new__', '__del__','__str__', '__repr__', '__format__','__eq__', '__ne__', '__lt__', '__le__', '__gt__', '__ge__', '__hash__','__bool__', '__len__','__getitem__', '__setitem__', '__delitem__', '__contains__','__iter__', '__next__', '__reversed__','__call__','__enter__', '__exit__','__add__', '__sub__', '__mul__', '__truediv__', '__mod__', '__pow__','__getattr__', '__setattr__', '__delattr__', '__getattribute__','__dir__', '__sizeof__' ] implemented = [] not_implemented = []for method in all_magic_methods:if hasattr(obj.__class__, method):# 获取方法来源 cls_method = getattr(obj.__class__, method)if (inspect.isfunction(cls_method) or inspect.ismethod(cls_method) or isinstance(cls_method, property)): implemented.append(method)else: not_implemented.append(method)else: not_implemented.append(method) print(f"\n已实现的魔法方法 ({len(implemented)}个):")for method in sorted(implemented): cls_method = getattr(obj.__class__, method) print(f" ✓ {method}: 定义于 {cls_method.__module__}.{cls_method.__qualname__}") print(f"\n未实现的魔法方法 ({len(not_implemented)}个):")for method in sorted(not_implemented)[:10]: # 只显示前10个 print(f" ✗ {method}")if len(not_implemented) > 10: print(f" ... 还有 {len(not_implemented) - 10} 个")return implemented, not_implemented# 分析自定义类classSampleClass:def__init__(self, x): self.x = xdef__str__(self):returnf"Sample({self.x})"def__eq__(self, other):return self.x == other.xobj = SampleClass(10)analyze_magic_methods(obj)
技巧3:使用 __slots__ 优化内存和调试
classWithSlots:"""使用__slots__优化内存和防止动态属性""" __slots__ = ('x', 'y', '_z')def__init__(self, x, y, z): self.x = x self.y = y self._z = z # 仍然可以有"私有"属性def__str__(self):returnf"WithSlots(x={self.x}, y={self.y}, z={self._z})"classWithoutSlots:"""不使用__slots__的传统类"""def__init__(self, x, y, z): self.x = x self.y = y self._z = zdef__str__(self):returnf"WithoutSlots(x={self.x}, y={self.y}, z={self._z})"# 比较内存使用import sysobj_slots = WithSlots(1, 2, 3)obj_no_slots = WithoutSlots(1, 2, 3)print(f"WithSlots 内存大小: {sys.getsizeof(obj_slots) + sys.getsizeof(obj_slots.__dict__ if hasattr(obj_slots, '__dict__') else0)} 字节")print(f"WithoutSlots 内存大小: {sys.getsizeof(obj_no_slots) + sys.getsizeof(obj_no_slots.__dict__)} 字节")# 尝试添加动态属性obj_no_slots.new_attr = "动态添加"# 成功print(f"传统类可以添加动态属性: {hasattr(obj_no_slots, 'new_attr')}")try: obj_slots.new_attr = "动态添加"# 失败 print("slots类可以添加动态属性")except AttributeError as e: print(f"slots类不能添加动态属性: {e}")# slots类没有__dict__print(f"传统类有__dict__: {hasattr(obj_no_slots, '__dict__')}")print(f"slots类有__dict__: {hasattr(obj_slots, '__dict__')}")
技巧4:自定义异常信息
classValidatedVector:"""带验证的向量类,提供清晰的错误信息"""def__init__(self, x, y): self._validate_input(x, "x") self._validate_input(y, "y") self.x = x self.y = ydef_validate_input(self, value, name):"""验证输入值"""ifnot isinstance(value, (int, float)):raise TypeError(f"{self.__class__.__name__}.{name} 必须是数字,"f"但收到 {type(value).__name__} 类型: {value!r}" )ifnot (0 <= value <= 100): # 示例约束raise ValueError(f"{self.__class__.__name__}.{name} 必须在0-100之间,"f"但收到: {value}" )def__add__(self, other):ifnot isinstance(other, ValidatedVector):raise TypeError(f"无法将 {type(other).__name__} 与 {self.__class__.__name__} 相加。"f"请提供另一个 {self.__class__.__name__} 实例。" )return ValidatedVector(self.x + other.x, self.y + other.y)def__str__(self):returnf"ValidatedVector({self.x}, {self.y})"# 测试清晰的错误信息try: v1 = ValidatedVector(10, 20) v2 = ValidatedVector(30, "invalid") # 会抛出清晰的错误except (TypeError, ValueError) as e: print(f"清晰的错误信息:\n{e}")try: v3 = ValidatedVector(10, 20) result = v3 + "不是向量"# 类型错误except TypeError as e: print(f"\n操作错误信息:\n{e}")
提示:
- 避免常见陷阱:理解
__new__ 和 __init__ 的区别,配对实现 __eq__ 和 __hash__ - 利用
__slots__ 优化内存使用和防止意外属性
6. 魔法方法分类速查表与进阶实践
6.1 魔法方法分类速查表
| | | |
|---|
| 构造/析构 | __new__(cls, ...) | MyClass() | |
| __init__(self, ...) | obj = MyClass() | |
| __del__(self) | del obj | |
| 字符串表示 | __str__(self) | str(obj) | |
| __repr__(self) | repr(obj) | |
| __format__(self, format_spec) | format(obj, spec) | |
| __bytes__(self) | bytes(obj) | |
| 比较运算 | __eq__(self, other) | == | |
| __ne__(self, other) | != | |
| __lt__(self, other) | < | |
| __le__(self, other) | <= | |
| __gt__(self, other) | > | |
| __ge__(self, other) | >= | |
| __hash__(self) | hash(obj) | |
| 数值运算 | __add__(self, other) | + | |
| __sub__(self, other) | - | |
| __mul__(self, other) | * | |
| __truediv__(self, other) | / | |
| __floordiv__(self, other) | // | |
| __mod__(self, other) | % | |
| __pow__(self, other) | ** | |
| __matmul__(self, other) | @ | |
| __neg__(self) | -obj | |
| __pos__(self) | +obj | |
| __abs__(self) | abs(obj) | |
| __invert__(self) | ~obj | |
| 类型转换 | __bool__(self) | bool(obj) | |
| __int__(self) | int(obj) | |
| __float__(self) | float(obj) | |
| __complex__(self) | complex(obj) | |
| __index__(self) | operator.index() | |
| 容器协议 | __len__(self) | len(obj) | |
| __getitem__(self, key) | obj[key] | |
| __setitem__(self, key, value) | obj[key] = value | |
| __delitem__(self, key) | del obj[key] | |
| __contains__(self, item) | item in obj | |
| __iter__(self) | iter(obj) | |
| __next__(self) | next(iterator) | |
| __reversed__(self) | reversed(obj) | |
| 可调用对象 | __call__(self, ...) | obj(...) | |
| 上下文管理 | __enter__(self) | with obj: | |
| __exit__(self, exc_type, ...) | | |
| 属性访问 | __getattr__(self, name) | obj.name | |
| __getattribute__(self, name) | obj.name | |
| __setattr__(self, name, value) | obj.name = value | |
| __delattr__(self, name) | del obj.name | |
| __dir__(self) | dir(obj) | |
| 描述符协议 | __get__(self, instance, owner) | | |
| __set__(self, instance, value) | | |
| __delete__(self, instance) | | |
| 复制协议 | __copy__(self) | copy.copy(obj) | |
| __deepcopy__(self, memo) | copy.deepcopy(obj) | |
| 异步支持 | __aiter__(self) | async for | |
| __anext__(self) | async for | |
| __aenter__(self) | async with | |
| __aexit__(self, ...) | | |
| __await__(self) | await obj | |
6.2 与内置装饰器结合使用
使用 @dataclass 自动生成魔法方法
from dataclasses import dataclass, field, asdict, astuplefrom typing import List, Optional@dataclass(order=True, frozen=True) # order=True生成比较方法,frozen=True使实例不可变classPerson:"""使用dataclass自动生成__init__, __repr__, __eq__等""" name: str age: int email: Optional[str] = None hobbies: List[str] = field(default_factory=list)# 可以自定义方法defis_adult(self) -> bool:return self.age >= 18# 可以自定义属性 @propertydefdisplay_name(self) -> str:returnf"{self.name} ({self.age})"# 自动生成的功能p1 = Person("Alice", 25, "alice@example.com", ["reading", "hiking"])p2 = Person("Bob", 30)p3 = Person("Alice", 25)print(f"p1: {p1}") # 自动生成 __repr__print(f"p1 == p3: {p1 == p3}") # True (自动生成 __eq__)print(f"p1 < p2: {p1 < p2}") # True (按字段顺序比较)print(f"p1.is_adult(): {p1.is_adult()}") # Trueprint(f"p1.display_name: {p1.display_name}") # Alice (25)# 转换为字典和元组print(f"asdict: {asdict(p1)}")print(f"astuple: {astuple(p1)}")# frozen=True使实例不可变try: p1.age = 26# 报错:dataclasses.FrozenInstanceErrorexcept Exception as e: print(f"不能修改frozen对象: {e}")
使用 @total_ordering 简化比较操作
from functools import total_orderingimport math@total_orderingclassCircle:"""圆形类,使用total_ordering自动生成完整比较操作"""def__init__(self, radius): self.radius = radius @propertydefarea(self):return math.pi * self.radius ** 2 @propertydefcircumference(self):return2 * math.pi * self.radiusdef__eq__(self, other):ifnot isinstance(other, Circle):returnNotImplementedreturn self.radius == other.radiusdef__lt__(self, other):ifnot isinstance(other, Circle):returnNotImplementedreturn self.radius < other.radiusdef__str__(self):returnf"Circle(r={self.radius})"# 使用total_ordering自动获得所有比较操作c1 = Circle(5)c2 = Circle(10)c3 = Circle(5)print(f"c1 == c3: {c1 == c3}") # Trueprint(f"c1 != c2: {c1 != c2}") # Trueprint(f"c1 < c2: {c1 < c2}") # Trueprint(f"c1 <= c2: {c1 <= c2}") # Trueprint(f"c2 > c1: {c2 > c1}") # Trueprint(f"c2 >= c1: {c2 >= c1}") # True# 可以排序circles = [Circle(3), Circle(1), Circle(2)]circles.sort()print(f"排序后的圆: {[c.radius for c in circles]}") # [1, 2, 3]
使用 @property 创建计算属性
classTemperature:"""温度类,使用property创建智能属性"""def__init__(self, celsius): self._celsius = celsius @propertydefcelsius(self):"""摄氏温度"""return self._celsius @celsius.setterdefcelsius(self, value):"""设置摄氏温度,带有验证"""if value < -273.15:raise ValueError("温度不能低于绝对零度(-273.15°C)") self._celsius = value @propertydeffahrenheit(self):"""华氏温度(计算属性)"""return self._celsius * 9/5 + 32 @fahrenheit.setterdeffahrenheit(self, value):"""通过华氏温度设置摄氏温度""" self._celsius = (value - 32) * 5/9 @propertydefkelvin(self):"""开氏温度(计算属性)"""return self._celsius + 273.15 @kelvin.setter defkelvin(self, value):"""通过开氏温度设置摄氏温度""" self._celsius = value - 273.15def__str__(self):returnf"{self.celsius:.1f}°C = {self.fahrenheit:.1f}°F = {self.kelvin:.1f}K"# 使用智能属性temp = Temperature(25)print(f"初始温度: {temp}")# 修改摄氏温度temp.celsius = 30print(f"修改后: {temp}")# 通过华氏温度设置temp.fahrenheit = 77print(f"设为77°F后: {temp}")# 通过开氏温度设置temp.kelvin = 300print(f"设为300K后: {temp}")# 属性验证try: temp.celsius = -300# 低于绝对零度except ValueError as e: print(f"验证错误: {e}")
使用 __slots__ 优化性能
import timeitfrom pympler import asizeof # 需要安装: pip install pymplerclassRegularPoint:"""普通类,使用__dict__存储属性"""def__init__(self, x, y, z): self.x = x self.y = y self.z = zdefdistance(self):return (self.x ** 2 + self.y ** 2 + self.z ** 2) ** 0.5classSlotsPoint:"""使用__slots__优化的类""" __slots__ = ('x', 'y', 'z')def__init__(self, x, y, z): self.x = x self.y = y self.z = zdefdistance(self):return (self.x ** 2 + self.y ** 2 + self.z ** 2) ** 0.5defbenchmark():"""性能对比基准测试""" print("=== 内存使用对比 ===")# 创建大量对象 regular_points = [RegularPoint(i, i+1, i+2) for i in range(1000)] slots_points = [SlotsPoint(i, i+1, i+2) for i in range(1000)]# 内存使用 regular_memory = sum(asizeof.asizeof(p) for p in regular_points[:10]) * 100# 估算 slots_memory = sum(asizeof.asizeof(p) for p in slots_points[:10]) * 100# 估算 print(f"1000个RegularPoint对象 ≈ {regular_memory / 1024:.1f} KB") print(f"1000个SlotsPoint对象 ≈ {slots_memory / 1024:.1f} KB") print(f"内存节省: {(regular_memory - slots_memory) / regular_memory * 100:.1f}%") print("\n=== 属性访问速度对比 ===")# 属性访问速度测试 regular_point = RegularPoint(1, 2, 3) slots_point = SlotsPoint(1, 2, 3)defaccess_regular(): _ = regular_point.x regular_point.x = 10defaccess_slots(): _ = slots_point.x slots_point.x = 10# 运行基准测试 regular_time = timeit.timeit(access_regular, number=1000000) slots_time = timeit.timeit(access_slots, number=1000000) print(f"RegularPoint属性访问: {regular_time:.3f}秒 (100万次)") print(f"SlotsPoint属性访问: {slots_time:.3f}秒 (100万次)") print(f"速度提升: {(regular_time - slots_time) / regular_time * 100:.1f}%") print("\n=== 动态属性测试 ===")# 动态添加属性 regular_point.new_attr = "可以添加" print(f"RegularPoint可以添加动态属性: {hasattr(regular_point, 'new_attr')}")try: slots_point.new_attr = "不能添加" print(f"SlotsPoint可以添加动态属性")except AttributeError: print(f"SlotsPoint不能添加动态属性(这是设计特性)")if __name__ == "__main__": benchmark()
使用描述符创建高级属性
classValidatedAttribute:"""描述符:验证属性值"""def__init__(self, min_value=None, max_value=None, allowed_types=None): self.min_value = min_value self.max_value = max_value self.allowed_types = allowed_types self.private_name = Nonedef__set_name__(self, owner, name):"""设置属性名""" self.private_name = '_' + namedef__get__(self, obj, objtype=None):"""获取属性值"""if obj isNone:return selfreturn getattr(obj, self.private_name, None)def__set__(self, obj, value):"""设置属性值,带验证"""# 类型检查if self.allowed_types andnot isinstance(value, self.allowed_types):raise TypeError(f"属性必须是 {self.allowed_types} 类型")# 最小值检查if self.min_value isnotNoneand value < self.min_value:raise ValueError(f"属性值不能小于 {self.min_value}")# 最大值检查if self.max_value isnotNoneand value > self.max_value:raise ValueError(f"属性值不能大于 {self.max_value}") setattr(obj, self.private_name, value)classPersonWithValidation:"""使用描述符验证属性的Person类"""# 使用描述符定义属性 age = ValidatedAttribute(min_value=0, max_value=150, allowed_types=(int,)) height = ValidatedAttribute(min_value=0.0, max_value=3.0, allowed_types=(int, float)) name = ValidatedAttribute(allowed_types=(str,))def__init__(self, name, age, height): self.name = name # 通过描述符设置 self.age = age # 通过描述符设置 self.height = height # 通过描述符设置def__str__(self):returnf"{self.name}, {self.age}岁, {self.height:.2f}米"# 测试描述符验证try: person = PersonWithValidation("张三", 25, 1.75) print(f"创建成功: {person}")# 测试验证 person.age = 30# 正常 print(f"修改年龄后: {person}") person.age = -5# 会触发验证错误except ValueError as e: print(f"验证错误: {e}")try: person.name = 123# 类型错误except TypeError as e: print(f"类型错误: {e}")
使用元类控制类创建
classAutoRegisterMeta(type):"""元类:自动注册所有子类""" registry = {}def__new__(mcs, name, bases, namespace):# 创建新类 cls = super().__new__(mcs, name, bases, namespace)# 如果不是基类,则注册if name != 'BaseModel': mcs.registry[name] = cls print(f"已注册类: {name}")return clsdef__init__(cls, name, bases, namespace): super().__init__(name, bases, namespace)# 为类添加类属性 cls._registered = True# 自动添加__repr__方法if'__repr__'notin namespace:defauto_repr(self): attrs = ', '.join(f"{k}={v!r}"for k, v in self.__dict__.items())returnf"{name}({attrs})" cls.__repr__ = auto_reprclassBaseModel(metaclass=AutoRegisterMeta):"""基类,使用元类"""pass# 创建子类classUser(BaseModel):def__init__(self, name, age): self.name = name self.age = ageclassProduct(BaseModel):def__init__(self, name, price): self.name = name self.price = price# 测试print(f"\n已注册的类: {list(AutoRegisterMeta.registry.keys())}")user = User("Alice", 25)product = Product("Book", 29.99)print(f"\n自动生成的__repr__:")print(f"user: {user}")print(f"product: {product}")print(f"\n类属性:")print(f"User._registered: {User._registered}")print(f"Product._registered: {Product._registered}")
提示:
- 使用内置装饰器如
@dataclass、@total_ordering 简化代码
总结
魔法方法是 Python 的协议系统:通过实现特定方法,让自定义对象支持内置类型的操作
- 实现
__eq__ 必须同时实现 __hash__ - 实现
__lt__ 可以使用 @total_ordering 简化
容器协议应完整实现:__len__、__getitem__、__setitem__、__iter__ 等
学习建议
初级阶段(掌握基础)
中级阶段(进阶应用)
__getitem__ 和 __setitem__:支持索引操作__enter__ 和 __exit__:上下文管理器
高级阶段(深入理解)
__getattr__ 和 __getattribute__:属性访问控制
专家阶段(系统设计)
实用建议
1. 按需实现,避免过度设计
# 简单情况使用dataclassfrom dataclasses import dataclass@dataclassclassPoint: x: float y: float# 需要特殊行为时才自定义魔法方法classSpecialPoint:def__init__(self, x, y): self.x = x self.y = ydef__add__(self, other):return SpecialPoint(self.x + other.x, self.y + other.y)
2. 保持一致性
- 如果实现了
__eq__,必须实现 __hash__
3. 提供清晰的错误信息
classVector:def__add__(self, other):ifnot isinstance(other, Vector):raise TypeError(f"只能与Vector实例相加,但收到 {type(other).__name__}" )# ...
4. 编写测试
import unittestclassTestVector(unittest.TestCase):deftest_addition(self): v1 = Vector(1, 2) v2 = Vector(3, 4) result = v1 + v2 self.assertEqual(result.x, 4) self.assertEqual(result.y, 6)deftest_invalid_addition(self): v1 = Vector(1, 2)with self.assertRaises(TypeError): v1 + "不是向量"
5. 参考标准库实现
# 学习 collections.abc 中的抽象基类from collections.abc import Sequence, MutableSequenceclassMyList(MutableSequence):"""通过继承抽象基类实现完整协议"""def__init__(self): self._data = []def__getitem__(self, index):return self._data[index]# 必须实现所有抽象方法def__setitem__(self, index, value): self._data[index] = valuedef__delitem__(self, index):del self._data[index]def__len__(self):return len(self._data)definsert(self, index, value): self._data.insert(index, value)
常见场景
| | |
|---|
| 创建不可变对象 | __slots__ | |
| 实现数值类型 | __add__ | |
| 创建容器类 | __len__, __getitem__, __iter__ 等 | |
| 实现上下文管理 | __enter__ | |
| 创建装饰器 | __call__ | |
| 实现状态机 | __call__ | |
| 创建DSL | | |
一些思考
魔法方法不是魔法,而是 Python 设计哲学的体现:
最好的代码是自解释的代码。当你合理使用魔法方法时,你的代码会变得更加:
- 直观:
vector1 + vector2 比 vector_add(vector1, vector2) 更直观 - 简洁:
for item in my_container 比手动迭代更简洁
通过掌握魔法方法,不仅可以学会 Python 的一个技术特性,更能深入理解 Python 的 "Pythonic" 设计哲学。这种理解将帮助我们在任何 Python 项目中编写更优雅、更强大、更易维护的代码。
喊一句口号:理解 → 实践 → 掌握 → 创新,不断积累,让自己更专业,让我们的 Python 代码变得更加 "Pythonic"!