Python 魔术方法详解
一、魔术方法详解
魔术方法(Magic Methods),也称为特殊方法(Special Methods)或双下方法(Dunder Methods),是 Python 中以双下划线开头和结尾的方法(例如 , )。它们不是让你直接调用的,而是由 Python 解释器在特定场景下自动调用的。它们为自定义类提供了与 Python 内置类型和操作符交互的能力,让你可以定义对象在各种操作下的行为。
Python 魔术方法(特殊方法),涵盖构造销毁、字符串表示、比较运算符、算术重载、类型转换、容器模拟、可调用对象、上下文管理器及属性访问控制等核心概念。通过 Point、Vector、Matrix 等代码示例,展示了如何利用双下划线方法定制类行为,实现运算符重载、资源管理及自定义逻辑,是 Python 面向对象编程的重要机制。

魔术方法(Magic Methods),也称为特殊方法(Special Methods)或双下方法(Dunder Methods),是 Python 中以双下划线开头和结尾的方法(例如 , )。它们不是让你直接调用的,而是由 Python 解释器在特定场景下自动调用的。它们为自定义类提供了与 Python 内置类型和操作符交互的能力,让你可以定义对象在各种操作下的行为。
__init____str____init__, __new__, __del__)__del__(self):析构器方法。当对象的引用计数变为零(或垃圾回收器准备回收对象)时被调用。谨慎使用!因为 Python 的内存管理(引用计数和垃圾回收)使得很难精确预测 __del__ 何时被调用。通常不需要手动实现,更好的资源清理方式是使用上下文管理器 (with 语句)。__new__(cls, [...]):在 __init__之前调用。它负责创建并返回类的一个新实例。它接收类本身(cls)作为第一个参数。通常不需要覆盖,但在需要控制实例创建过程(如单例模式、不可变对象、子类化不可变类型)时很有用。
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # 输出:True
__init__(self, [...]):最常用的魔术方法。在对象实例创建之后被调用。用于初始化新实例的属性(设置初始状态)。它接受传递给类构造函数的参数(通常是 __init__(self, param1, param2, ...))。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4) # 调用 Point.__init__(p, 3, 4)
__str__, __repr__)__str__(self):当调用 str(object)、print(object) 或 format(object) 时调用。目的是返回一个对用户友好、可读性高的字符串表示,描述对象的内容。__repr__(self):当调用 repr(object) 或在交互式解释器中直接输入对象名称时调用。目的是返回一个对开发者友好、明确的字符串表示,通常是一个可以用来重新创建该对象的有效 Python 表达式(如果可能的话)。如果 __str__ 未定义,__repr__ 会作为其默认回退。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __repr__(self):
return f"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__)
__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__)。
class Point:
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) # True
print(p1 != p3) # True (默认使用 not __eq__)
print(p1 < p3) # True
__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。
class Vector:
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 # 通常返回 self
def __repr__(self):
return f"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)
__int__, __float__, __bool__, __complex__)int(), float(), bool(), complex() 时的行为。__bool__(self):特别重要,定义了对象在布尔上下文中(如 if obj:)的真假值。如果未定义 __bool__,Python 会尝试调用 __len__()(如果定义了),非零长度则为真,否则为假。如果两者都未定义,则所有实例都被视为真。
class Truthy:
def __bool__(self):
return True
class Falsy:
def __bool__(self):
return False
class NoBoolButLen:
def __len__(self):
return 0
if Truthy():
print("True") # 输出 True
if Falsy():
print("This won't") # 不输出
if NoBoolButLen():
print("This won't either") # 不输出 (__len__ 返回 0)
__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) 的行为,返回一个反向迭代器。
class MyList:
def __init__(self, data):
self.data = list(data)
def __len__(self):
return len(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 = 0
return self
def __next__(self):
if self.current < len(self.data):
item = self.data[self.current]
self.current += 1
return item
else:
raise StopIteration
my_list = MyList([1, 2, 3])
print(len(my_list)) # 3
print(my_list[1]) # 2
my_list[1] = 20
print(2 in my_list) # False
print(20 in my_list) # True
for item in my_list: # 1, 20, 3
print(item)
__call__)__call__(self, [...]):定义对象本身像函数一样被调用时的行为 obj(...)。这使得实例可以像函数一样使用。
class Adder:
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) # 8
__enter__, __exit__)with 语句中,用于资源管理(如自动关闭文件)。__enter__(self):在进入 with 块时调用,其返回值会赋值给 as 后的变量(如果有)。__exit__(self, exc_type, exc_value, traceback):在退出 with 块时调用(无论正常退出还是异常退出)。它接收异常信息参数。如果返回 True 或真值,则表示异常已被处理,不会向外传播;返回 None 或假值,则异常会向外传播。
class ManagedFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
# 可以在这里处理异常,返回 True 表示已处理
return False # 让异常正常传播
with ManagedFile('example.txt', 'w') as f:
f.write('Hello, world!') # 文件在这里自动关闭
__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):当试图删除一个属性时调用。
class Lazy:
def __init__(self):
self._expensive_data = None
def __getattr__(self, name):
if name == 'expensive_data':
if self._expensive_data is None:
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) # 第二次访问直接返回缓存
__hash__(self):定义 hash(obj) 的行为,返回一个整数哈希值。如果定义了 __eq__,通常也应该定义 __hash__(除非对象是可变的)。哈希值应满足:相等的对象必须有相等的哈希值(但不要求不等的对象哈希值不同)。不可变对象通常需要实现。__dir__(self):定义 dir(obj) 的行为,返回一个包含对象属性名称的列表。魔术方法是 Python 面向对象编程的核心机制之一。它们允许你:
__init__)__str__, __repr__)__eq__, __lt__, …)__add__, __sub__, …)__len__, __getitem__, …)__call__)with 语句 (__enter__, __exit__)__getattr__, __setattr__, …)__int__, __float__, __bool__)__hash__)import math
class Matrix:
def __init__(self, rows, cols=None, data=None):
"""初始化矩阵
参数:
rows: 行数或二维列表
cols: 列数(当 rows 为整数时)
data: 初始数据(可选)
"""
if isinstance(rows, list):
# 从二维列表创建
self.data = rows
self.rows = len(rows)
self.cols = len(rows[0]) if self.rows > 0 else 0
else:
# 指定行列创建
self.rows = rows
self.cols = cols if cols is not None else rows
self.data = [[0] * self.cols for _ in range(self.rows)]
if data:
for i in range(min(self.rows, len(data))):
for j in range(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):
"""返回可执行的表示形式"""
return f"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 in range(self.cols)] for i in range(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 in range(self.cols)] for i in range(self.rows)])
def __mul__(self, other):
"""矩阵乘法(支持标量和矩阵)"""
if isinstance(other, (int, float)): # 标量乘法
return Matrix([[x * other for x in row] for row in self.data])
elif isinstance(other, Matrix): # 矩阵乘法
if self.cols != other.rows:
raise ValueError("矩阵维度不匹配")
result = Matrix(self.rows, other.cols)
for i in range(self.rows):
for j in range(other.cols):
result[i, j] = sum(self[i, k] * other[k, j] for k in range(self.cols))
return result
else:
raise TypeError("不支持的乘法类型")
def __rmul__(self, other):
"""右乘标量"""
if isinstance(other, (int, float)):
return self * other
raise TypeError("不支持的乘法类型")
def __matmul__(self, other):
"""矩阵乘法运算符 @ (Python 3.5+)"""
return self * other
def __getitem__(self, index):
"""索引访问"""
if isinstance(index, tuple) and len(index) == 2:
i, j = index
return self.data[i][j]
elif isinstance(index, int):
return self.data[index]
raise IndexError("无效索引")
def __setitem__(self, index, value):
"""索引赋值"""
if isinstance(index, tuple) and len(index) == 2:
i, j = index
self.data[i][j] = value
elif isinstance(index, int):
self.data[index] = value
else:
raise IndexError("无效索引")
def __eq__(self, other):
"""矩阵相等比较"""
return (
self.rows == other.rows
and self.cols == other.cols
and all(
self.data[i][j] == other.data[i][j]
for i in range(self.rows)
for j in range(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 = 0
for j in range(n):
minor = [[matrix[i][k] for k in range(n) if k != j] for i in range(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 in range(self.rows)] for i in range(self.cols)])
def __call__(self, vector):
"""将矩阵作为线性变换函数"""
if not isinstance(vector, list) or len(vector) != self.cols:
raise ValueError("输入必须是长度为矩阵列数的列表")
return [sum(self[i, j] * vector[j] for j in range(self.cols)) for i in range(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

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online