python 魔术方法详解
python 魔术方法详解
- 一、魔术方法详解
- 1、核心概念
- 2、常见的魔术方法分类及详解
- 2.1、构造与销毁 (`__init__`, `__new__`, `__del__`)
- 2.2、字符串表示 (`__str__`, `__repr__`)
- 2.3、比较操作符 (`__eq__`, `__ne__`, `__lt__`, `__le__`, `__gt__`, `__ge__`)
- 2.4、算术运算符重载 (`__add__`, `__sub__`, `__mul__`, `__truediv__`, `__floordiv__`, `__mod__`, `__pow__`, ... 及其 `__r*__` 和 `__i*__` 变体)
- 2.5、类型转换 (`__int__`, `__float__`, `__bool__`, `__complex__`)
- 2.6、容器/序列模拟 (`__len__`, `__getitem__`, `__setitem__`, `__delitem__`, `__contains__`, `__iter__`, `__reversed__`, `__next__`)
- 2.7、可调用对象 (`__call__`)
- 2.8、上下文管理器 (`__enter__`, `__exit__`)
- 2.9、 属性访问控制 (`__getattr__`, `__getattribute__`, `__setattr__`, `__delattr__`)
- 2.10、其他重要方法
- 3、总结
- 二、代码示例
一、魔术方法详解
魔术方法(Magic Methods),也称为特殊方法(Special Methods)或双下方法(Dunder Methods),是 Python 中以双下划线开头和结尾的方法(例如 __init__, __str__)。它们不是让你直接调用的,而是由 Python 解释器在特定场景下自动调用的。它们为自定义类提供了与 Python 内置类型和操作符交互的能力,让你可以定义对象在各种操作下的行为。
1、核心概念
- 自动调用:你定义它们,但 Python 在特定事件发生时(如对象初始化、加法运算、打印对象等)自动调用它们。
- 增强功能:它们赋予你的类特殊的行为,使其可以像内置类型一样工作。
- 约定俗成:每个魔术方法都有特定的名称和预期的行为。遵循这些约定很重要。
2、常见的魔术方法分类及详解
2.1、构造与销毁 (__init__, __new__, __del__)
__del__(self):析构器方法。当对象的引用计数变为零(或垃圾回收器准备回收对象)时被调用。谨慎使用!因为 Python 的内存管理(引用计数和垃圾回收)使得很难精确预测__del__何时被调用。通常不需要手动实现,更好的资源清理方式是使用上下文管理器 (with语句)。
__new__(cls, [...]):在 __init__之前调用。它负责创建并返回类的一个新实例。它接收类本身(cls)作为第一个参数。通常不需要覆盖,但在需要控制实例创建过程(如单例模式、不可变对象、子类化不可变类型)时很有用。
classSingleton: _instance =Nonedef__new__(cls):if cls._instance isNone: cls._instance =super().__new__(cls)return cls._instance s1 = Singleton() s2 = Singleton()print(s1 is s2)# 输出: True__init__(self, [...]):最常用的魔术方法。在对象实例创建之后被调用。用于初始化新实例的属性(设置初始状态)。它接受传递给类构造函数的参数(通常是 __init__(self, param1, param2, ...))。
classPoint:def__init__(self, x, y): self.x = x self.y = y p = Point(3,4)# 调用 Point.__init__(p, 3, 4)2.2、字符串表示 (__str__, __repr__)
__str__(self):当调用str(object)、print(object)或format(object)时调用。目的是返回一个对用户友好、可读性高的字符串表示,描述对象的内容。
__repr__(self):当调用 repr(object) 或在交互式解释器中直接输入对象名称时调用。目的是返回一个对开发者友好、明确的字符串表示,通常是一个可以用来重新创建该对象的有效 Python 表达式(如果可能的话)。如果 __str__ 未定义,__repr__ 会作为其默认回退。
classPoint:def__init__(self, x, y): self.x = x self.y = y def__str__(self):returnf"Point({self.x}, {self.y})"def__repr__(self):returnf"Point(x={self.x}, y={self.y})" p = Point(3,4)print(p)# 输出: Point(3, 4) (调用 __str__)print(repr(p))# 输出: Point(x=3, y=4) (调用 __repr__)2.3、比较操作符 (__eq__, __ne__, __lt__, __le__, __gt__, __ge__)
- 这些方法定义了对象在使用
==,!=,<,<=,>,>=操作符时的行为。 __eq__(self, other):定义self == other的行为。__ne__(self, other):定义self != other的行为。默认实现通常是not self == other。__lt__(self, other):定义self < other的行为。__le__(self, other):定义self <= other的行为。__gt__(self, other):定义self > other的行为。__ge__(self, other):定义self >= other的行为。
如果只实现了其中几个(如 __lt__),Python 可能尝试用它们推导出其他比较操作(但这并不总是可靠)。使用 functools.total_ordering 装饰器可以自动补全缺失的比较方法,只需定义 __eq__ 和另一个(如 __lt__)。
classPoint:def__init__(self, x, y): self.x = x self.y = y def__eq__(self, other):return self.x == other.x and self.y == other.y def__lt__(self, other):# 定义小于,通常用于排序return(self.x **2+ self.y **2)<(other.x **2+ other.y **2) p1 = Point(1,2) p2 = Point(1,2) p3 = Point(3,4)print(p1 == p2)# Trueprint(p1 != p3)# True (默认使用 not __eq__)print(p1 < p3)# True2.4、算术运算符重载 (__add__, __sub__, __mul__, __truediv__, __floordiv__, __mod__, __pow__, … 及其 __r*__ 和 __i*__ 变体)
- 这些方法定义了对象在使用
+,-,*,/,//,%,**等操作符时的行为。 __add__(self, other):定义self + other的行为。__sub__(self, other):定义self - other的行为。- … 其他类似。
__radd__(self, other),__rsub__(self, other), …:定义other + self,other - self的行为(当self是操作符右侧的操作数时调用)。这用于处理self不支持与other类型直接运算的情况。
__iadd__(self, other), __isub__(self, other), …:定义 self += other, self -= other 等原地操作的行为。如果未实现,Python 会尝试使用 __add__ / __sub__ 等,并将结果赋值给 self。
classVector:def__init__(self, x, y): self.x = x self.y = y def__add__(self, other):return Vector(self.x + other.x, self.y + other.y)def__iadd__(self, other):# 原地加法 += self.x += other.x self.y += other.y return self # 通常返回 selfdef__repr__(self):returnf"Vector({self.x}, {self.y})" v1 = Vector(1,2) v2 = Vector(3,4) v3 = v1 + v2 # 调用 v1.__add__(v2) -> Vector(4, 6) v1 += v2 # 调用 v1.__iadd__(v2) -> v1 变为 Vector(4, 6)2.5、类型转换 (__int__, __float__, __bool__, __complex__)
- 这些方法定义了对象在被传递给内置函数
int(),float(),bool(),complex()时的行为。
__bool__(self):特别重要,定义了对象在布尔上下文中(如 if obj:)的真假值。如果未定义 __bool__,Python 会尝试调用 __len__()(如果定义了),非零长度则为真,否则为假。如果两者都未定义,则所有实例都被视为真。
classTruthy:def__bool__(self):returnTrueclassFalsy:def__bool__(self):returnFalseclassNoBoolButLen:def__len__(self):return0if Truthy():print("True")# 输出 Trueif Falsy():print("This won't")# 不输出if NoBoolButLen():print("This won't either")# 不输出 (__len__ 返回 0)2.6、容器/序列模拟 (__len__, __getitem__, __setitem__, __delitem__, __contains__, __iter__, __reversed__, __next__)
- 这些方法让自定义类表现得像列表、字典、集合等容器。
__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)或for item in obj的行为。应该返回一个实现了__next__的迭代器对象(通常是self如果它自己实现了__next__)。__next__(self):定义迭代器对象在next(iterator)或for循环中的下一个值的行为。当没有更多元素时,必须引发StopIteration异常。
__reversed__(self):定义 reversed(obj) 的行为,返回一个反向迭代器。
classMyList:def__init__(self, data): self.data =list(data)def__len__(self):returnlen(self.data)def__getitem__(self, index):return self.data[index]def__setitem__(self, index, value): self.data[index]= value def__delitem__(self, index):del self.data[index]def__contains__(self, item):return item in self.data def__iter__(self): self.current =0return self def__next__(self):if self.current <len(self.data): item = self.data[self.current] self.current +=1return item else:raise StopIteration my_list = MyList([1,2,3])print(len(my_list))# 3print(my_list[1])# 2 my_list[1]=20print(2in my_list)# Falseprint(20in my_list)# Truefor item in my_list:# 1, 20, 3print(item)2.7、可调用对象 (__call__)
__call__(self, [...]):定义对象本身像函数一样被调用时的行为 obj(...)。这使得实例可以像函数一样使用。
classAdder:def__init__(self, base): self.base = base def__call__(self, x):return self.base + x add5 = Adder(5) result = add5(3)# 调用 add5.__call__(3)print(result)# 82.8、上下文管理器 (__enter__, __exit__)
- 这些方法使对象可以用在
with语句中,用于资源管理(如自动关闭文件)。 __enter__(self):在进入with块时调用,其返回值会赋值给as后的变量(如果有)。
__exit__(self, exc_type, exc_value, traceback):在退出 with 块时调用(无论正常退出还是异常退出)。它接收异常信息参数。如果返回 True 或真值,则表示异常已被处理,不会向外传播;返回 None 或假值,则异常会向外传播。
classManagedFile:def__init__(self, filename, mode): self.filename = filename self.mode = mode self.file=Nonedef__enter__(self): self.file=open(self.filename, self.mode)return self.filedef__exit__(self, exc_type, exc_value, traceback):if self.file: self.file.close()# 可以在这里处理异常,返回 True 表示已处理returnFalse# 让异常正常传播with ManagedFile('example.txt','w')as f: f.write('Hello, world!')# 文件在这里自动关闭2.9、 属性访问控制 (__getattr__, __getattribute__, __setattr__, __delattr__)
__getattr__(self, name):当试图访问一个不存在的属性时调用。name是属性名。可用于实现动态属性或惰性加载。__getattribute__(self, name):当试图访问任何属性(无论是否存在)时调用。谨慎使用!很容易导致无限递归(访问self.name会再次触发__getattribute__)。通常使用super().__getattribute__(name)来避免递归。覆盖它会显著影响性能。__setattr__(self, name, value):当试图给任何属性赋值时调用。同样要注意避免递归(赋值self.name = value会再次触发__setattr__)。通常使用super().__setattr__(name, value)或直接操作__dict__。
__delattr__(self, name):当试图删除一个属性时调用。
classLazy:def__init__(self): self._expensive_data =Nonedef__getattr__(self, name):if name =='expensive_data':if self._expensive_data isNone:print("Calculating expensive data...") self._expensive_data ="Computed Value"# 模拟计算return self._expensive_data raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") lazy_obj = Lazy()print(lazy_obj.expensive_data)# 第一次访问会计算print(lazy_obj.expensive_data)# 第二次访问直接返回缓存2.10、其他重要方法
__hash__(self):定义hash(obj)的行为,返回一个整数哈希值。如果定义了__eq__,通常也应该定义__hash__(除非对象是可变的)。哈希值应满足:相等的对象必须有相等的哈希值(但不要求不等的对象哈希值不同)。不可变对象通常需要实现。__dir__(self):定义dir(obj)的行为,返回一个包含对象属性名称的列表。
3、总结
魔术方法是 Python 面向对象编程的核心机制之一。它们允许你:
- 定制初始化 (
__init__) - 定义对象如何被表示 (
__str__,__repr__) - 控制比较行为 (
__eq__,__lt__, …) - 重载运算符 (
__add__,__sub__, …) - 模拟内置容器 (
__len__,__getitem__, …) - 使对象可调用 (
__call__) - 支持
with语句 (__enter__,__exit__) - 管理属性访问 (
__getattr__,__setattr__, …) - 提供类型转换 (
__int__,__float__,__bool__) - 定义哈希值 (
__hash__)
二、代码示例
import math classMatrix:def__init__(self, rows, cols=None, data=None):"""初始化矩阵 参数: rows: 行数或二维列表 cols: 列数(当rows为整数时) data: 初始数据(可选) """ifisinstance(rows,list):# 从二维列表创建 self.data = rows self.rows =len(rows) self.cols =len(rows[0])if self.rows >0else0else:# 指定行列创建 self.rows = rows self.cols = cols if cols isnotNoneelse rows self.data =[[0]*self.cols for _ inrange(self.rows)]if data:for i inrange(min(self.rows,len(data))):for j inrange(min(self.cols,len(data[i]))): self.data[i][j]= data[i][j]def__str__(self):"""美化输出矩阵""" max_len =max(len(str(x))for row in self.data for x in row)return"[\n"+"\n".join(" ["+", ".join(f"{x:>{max_len}}"for x in row)+"]"for row in self.data )+"\n]"def__repr__(self):"""返回可执行的表示形式"""returnf"Matrix({self.data})"def__add__(self, other):"""矩阵加法"""if self.rows != other.rows or self.cols != other.cols:raise ValueError("矩阵维度不匹配")return Matrix([[self.data[i][j]+ other.data[i][j]for j inrange(self.cols)]for i inrange(self.rows)])def__sub__(self, other):"""矩阵减法"""if self.rows != other.rows or self.cols != other.cols:raise ValueError("矩阵维度不匹配")return Matrix([[self.data[i][j]- other.data[i][j]for j inrange(self.cols)]for i inrange(self.rows)])def__mul__(self, other):"""矩阵乘法(支持标量和矩阵)"""ifisinstance(other,(int,float)):# 标量乘法return Matrix([[x * other for x in row]for row in self.data ])elifisinstance(other, Matrix):# 矩阵乘法if self.cols != other.rows:raise ValueError("矩阵维度不匹配") result = Matrix(self.rows, other.cols)for i inrange(self.rows):for j inrange(other.cols): result[i, j]=sum( self[i, k]* other[k, j]for k inrange(self.cols))return result else:raise TypeError("不支持的乘法类型")def__rmul__(self, other):"""右乘标量"""ifisinstance(other,(int,float)):return self * other raise TypeError("不支持的乘法类型")def__matmul__(self, other):"""矩阵乘法运算符 @ (Python 3.5+)"""return self * other def__getitem__(self, index):"""索引访问"""ifisinstance(index,tuple)andlen(index)==2: i, j = index return self.data[i][j]elifisinstance(index,int):return self.data[index]raise IndexError("无效索引")def__setitem__(self, index, value):"""索引赋值"""ifisinstance(index,tuple)andlen(index)==2: i, j = index self.data[i][j]= value elifisinstance(index,int): self.data[index]= value else:raise IndexError("无效索引")def__eq__(self, other):"""矩阵相等比较"""return( self.rows == other.rows and self.cols == other.cols andall( self.data[i][j]== other.data[i][j]for i inrange(self.rows)for j inrange(self.cols)))def__abs__(self):"""计算行列式"""if self.rows != self.cols:raise ValueError("只有方阵才有行列式")return self._determinant(self.data)def_determinant(self, matrix):"""递归计算行列式""" n =len(matrix)if n ==1:return matrix[0][0]if n ==2:return matrix[0][0]*matrix[1][1]- matrix[0][1]*matrix[1][0] det =0for j inrange(n): minor =[[matrix[i][k]for k inrange(n)if k != j]for i inrange(1, n)] sign =(-1)** j det += sign * matrix[0][j]* self._determinant(minor)return det def__invert__(self):"""矩阵转置"""return Matrix([[self.data[j][i]for j inrange(self.rows)]for i inrange(self.cols)])def__call__(self, vector):"""将矩阵作为线性变换函数"""ifnotisinstance(vector,list)orlen(vector)!= self.cols:raise ValueError("输入必须是长度为矩阵列数的列表")return[sum(self[i, j]* vector[j]for j inrange(self.cols))for i inrange(self.rows)]# 示例用法if __name__ =="__main__":# 创建矩阵 A = Matrix([[1,2,3],[4,5,6],[7,8,9]]) B = Matrix([[9,8,7],[6,5,4],[3,2,1]])print("矩阵 A:")print(A)print("\n矩阵 B:")print(B)print("\n矩阵加法 A + B:")print(A + B)print("\n矩阵乘法 A * B:")print(A * B)print("\n使用 @ 运算符的矩阵乘法 A @ B:")print(A @ B)print("\n标量乘法 2 * A:")print(2* A)print("\n矩阵转置 ~A:")print(~A)print("\n矩阵行列式 |A| =",abs(A))print("\n矩阵作为线性变换 A([1, 2, 3]) =", A([1,2,3]))print("\n矩阵相等比较 A == B:", A == B)运行结果:
矩阵 A:[[1,2,3][4,5,6][7,8,9]] 矩阵 B:[[9,8,7][6,5,4][3,2,1]] 矩阵加法 A + B:[[10,10,10][10,10,10][10,10,10]] 矩阵乘法 A * B:[[30,24,18][84,69,54][138,114,90]] 使用 @ 运算符的矩阵乘法 A @ B:[[30,24,18][84,69,54][138,114,90]] 标量乘法 2* A:[[2,4,6][8,10,12][14,16,18]] 矩阵转置 ~A:[[1,4,7][2,5,8][3,6,9]] 矩阵行列式 |A|=0 矩阵作为线性变换 A([1,2,3])=[14,32,50] 矩阵相等比较 A == B: False PS C:\Users\徐鹏\Desktop\新建文件夹 (2)>