在 Python 中,类的特殊方法(也称为"魔术方法"或"双下方法")是实现运算符重载和定制类行为的关键。这些方法以双下划线开头和结尾(如 __init__
),让开发者能够定义对象如何响应内置操作。
1. 什么是特殊方法?
特殊方法是 Python 中预定义的方法,用于实现类的特定行为:
2. 核心价值
- 自然语法:让自定义类支持
obj1 + obj2
等自然操作 - 代码简洁:减少样板代码,提高可读性
- 高度集成:使自定义类无缝融入 Python 生态系统
1. 对象创建与初始化
| | |
---|
__new__ | | obj = MyClass() |
__init__ | | obj = MyClass() |
__del__ | | |
class Resource:
def __init__(self, name):
self.name = name
print(f"资源 {name} 初始化")
def __del__(self):
print(f"资源 {self.name} 释放")
# 使用
res = Resource("数据库连接")
del res # 输出: 资源 数据库连接 释放
2. 字符串表示
| | |
---|
__str__ | | print(obj) |
__repr__ | | repr(obj) |
__format__ | | format(obj, spec) |
class Point:
def__init__(self, x, y):
self.x = x
self.y = y
def__str__(self):
returnf"点({self.x}, {self.y})"
def__repr__(self):
returnf"Point(x={self.x}, y={self.y})"
def__format__(self, format_spec):
if format_spec == 'polar':
r = (self.x**2 + self.y**2)**0.5
theta = math.degrees(math.atan2(self.y, self.x))
returnf"极坐标: ({r:.2f}, {theta:.1f}°)"
returnstr(self)
# 使用
p = Point(3, 4)
print(p) # 点(3, 4)
print(repr(p)) # Point(x=3, y=4)
print(f"{p}") # 点(3, 4)
print(f"{p:polar}") # 极坐标: (5.00, 53.1°)
3. 运算符重载
算术运算符
| | |
---|
__add__ | + | |
__sub__ | - | |
__mul__ | * | |
__truediv__ | / | |
__floordiv__ | // | |
__mod__ | % | |
__pow__ | ** | |
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__mul__(self, scalar):
ifisinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
raise TypeError("标量必须是数值")
def__str__(self):
returnf"({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 4)
print(v1 + v2) # (3, 7)
print(v1 * 3) # (6, 9)
比较运算符
| | |
---|
__eq__ | == | |
__ne__ | != | |
__lt__ | < | |
__le__ | <= | |
__gt__ | > | |
__ge__ | >= | |
class Temperature:
def__init__(self, celsius):
self.celsius = celsius
def__eq__(self, other):
returnself.celsius == other.celsius
def__lt__(self, other):
returnself.celsius < other.celsius
def__str__(self):
returnf"{self.celsius}°C"
t1 = Temperature(25)
t2 = Temperature(30)
print(t1 == t2) # False
print(t1 < t2) # True
反运算方法
当左操作数不支持运算时,Python 会尝试右操作数的反运算方法:
__radd__:other + self
__rsub__:other - self
__rmul__:other * self
class Vector:
# ... 其他方法同上
def __rmul__(self, scalar):
return self.__mul__(scalar)
v = Vector(2, 3)
print(3 * v) # 调用 __rmul__: (6, 9)
4. 容器类型方法
| | |
---|
__len__ | | len(obj) |
__getitem__ | | obj[key] |
__setitem__ | | obj[key] = value |
__delitem__ | | del obj[key] |
__contains__ | | item in obj |
class Matrix:
def__init__(self, rows, cols):
self.data = [[0] * cols for _ inrange(rows)]
self.rows = rows
self.cols = cols
def__len__(self):
returnself.rows
def__getitem__(self, index):
ifisinstance(index, int):
returnself.data[index]
elifisinstance(index, tuple) andlen(index) == 2:
row, col = index
returnself.data[row][col]
raise TypeError("无效索引")
def__setitem__(self, index, value):
ifisinstance(index, tuple) andlen(index) == 2:
row, col = index
self.data[row][col] = value
else:
raise TypeError("需要(row, col)索引")
def__contains__(self, value):
returnany(value in row for row inself.data)
# 使用
m = Matrix(3, 3)
m[1, 1] = 5# 设置中心元素
print(m[1, 1]) # 5
print(5in m) # True
5. 可调用对象
class Counter:
def__init__(self):
self.count = 0
def__call__(self):
self.count += 1
returnself.count
# 使用
counter = Counter()
print(counter()) # 1
print(counter()) # 2
6. 上下文管理
| | |
---|
__enter__ | | with obj: |
__exit__ | | |
class Timer:
def__enter__(self):
self.start = time.time()
returnself
def__exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
print(f"耗时: {self.end - self.start:.4f}秒")
defelapsed(self):
return time.time() - self.start
# 使用
with Timer() as t:
time.sleep(1.5)
print(f"已用时间: {t.elapsed():.2f}秒")
# 输出:
# 已用时间: 1.50秒
# 耗时: 1.5012秒
1. 属性访问控制
| | |
---|
__getattr__ | | obj.undefined |
__getattribute__ | | obj.any_attr |
__setattr__ | | obj.attr = value |
__delattr__ | | del obj.attr |
class ImmutablePoint:
def__init__(self, x, y):
super().__setattr__('x', x)
super().__setattr__('y', y)
def__setattr__(self, name, value):
raise AttributeError("ImmutablePoint 对象不可修改")
def__delattr__(self, name):
raise AttributeError("ImmutablePoint 对象不可删除属性")
# 使用
p = ImmutablePoint(3, 4)
print(p.x) # 3
p.x = 5 # AttributeError
2. 数值类型扩展
| |
---|
__abs__ | abs(obj) |
__neg__ | -obj |
__pos__ | +obj |
__invert__ | ~obj |
class ComplexNumber:
def__init__(self, real, imag):
self.real = real
self.imag = imag
def__abs__(self):
return (self.real**2 + self.imag**2)**0.5
def__neg__(self):
return ComplexNumber(-self.real, -self.imag)
def__str__(self):
returnf"{self.real}{self.imag:+}i"
# 使用
c = ComplexNumber(3, 4)
print(abs(c)) # 5.0
print(-c) # -3-4i
3. 反射方法
用于支持内置函数 isinstance()
和 issubclass()
:
| |
---|
__instancecheck__ | isinstance(obj, cls) |
__subclasscheck__ | issubclass(sub, cls) |
class Meta(type):
def__instancecheck__(cls, instance):
print(f"检查实例: {instance}")
returnhasattr(instance, 'special_attr')
def__subclasscheck__(cls, subclass):
print(f"检查子类: {subclass}")
returnhasattr(subclass, 'required_method')
classBase(metaclass=Meta):
pass
classChild(Base):
required_method = lambda: None
obj = type('X', (), {'special_attr': True})()
print(isinstance(obj, Base)) # True
print(issubclass(Child, Base)) # True
1. 设计原则
- 一致性:保持特殊方法行为与内置类型一致
- 最小惊讶原则:避免反直觉的实现
- 性能考量:复杂计算应考虑缓存或延迟计算
- 文档完备:为特殊方法添加详细文档字符串
2. 常见错误
# 错误1:无限递归
classBadExample:
def__init__(self, value):
self.value = value # 调用 __setattr__
def__setattr__(self, name, value):
self.name = value # 无限递归!
# 正确做法
classGoodExample:
def__init__(self, value):
super().__setattr__('value', value)
def__setattr__(self, name, value):
super().__setattr__(name, value)
# 错误2:忽略不可变类型
classVector:
def__init__(self, x, y):
self.x = x
self.y = y
def__add__(self, other):
# 错误:原地修改
self.x += other.x
self.y += other.y
returnself
# 正确做法(返回新对象)
classCorrectVector:
def__init__(self, x, y):
self.x = x
self.y = y
def__add__(self, other):
return CorrectVector(self.x + other.x, self.y + other.y)
3. 优化技巧
避免不必要的计算:
class LazyProperty:
def__init__(self, func):
self.func = func
self.name = func.__name__
def__get__(self, obj, cls):
if obj isNone:
returnself
value = self.func(obj)
setattr(obj, self.name, value) # 缓存结果
return value
classCircle:
def__init__(self, radius):
self.radius = radius
@LazyProperty
defarea(self):
print("计算面积...")
return3.14 * self.radius ** 2
c = Circle(5)
print(c.area) # 第一次计算
print(c.area) # 使用缓存
减少临时对象:
class Vector:
# ... 其他方法
def __iadd__(self, other):
"""实现 += 原地操作"""
self.x += other.x
self.y += other.y
return self
4. 使用场景推荐
| | |
---|
| __add__ | |
| __getitem__ | |
| __enter__ | |
| __call__ | |
| __getattribute__ | |
| __new__ | |
1. 实现自定义范围类型
class InclusiveRange:
"""支持步长的包含范围 [start, end]"""
def__init__(self, start, end, step=1):
if step == 0:
raise ValueError("步长不能为零")
self.start = start
self.end = end
self.step = step
def__iter__(self):
current = self.start
ifself.step > 0:
while current <= self.end:
yield current
current += self.step
else:
while current >= self.end:
yield current
current += self.step
def__len__(self):
ifself.step > 0:
returnmax(0, (self.end - self.start) // self.step + 1)
else:
returnmax(0, (self.start - self.end) // (-self.step) + 1)
def__contains__(self, item):
ifnotisinstance(item, (int, float)):
returnFalse
ifself.step > 0:
if item < self.start or item > self.end:
returnFalse
else:
if item > self.start or item < self.end:
returnFalse
return (item - self.start) % self.step == 0
def__reversed__(self):
return InclusiveRange(self.end, self.start, -self.step)
def__str__(self):
returnf"[{self.start}..{self.end}]" + (
f" step {self.step}"ifself.step != 1else""
)
# 使用
r = InclusiveRange(1, 10, 2)
print(list(r)) # [1, 3, 5, 7, 9]
print(len(r)) # 5
print(5in r) # True
print(list(reversed(r))) # [9, 7, 5, 3, 1]
2. 实现科学计算向量
class ScientificVector:
"""支持基本运算和科学函数的向量"""
def__init__(self, *components):
self.components = components
self.dim = len(components)
def__add__(self, other):
self._check_dim(other)
return ScientificVector(*(
x + y for x, y inzip(self.components, other.components)
)
def__sub__(self, other):
self._check_dim(other)
return ScientificVector(*(
x - y for x, y inzip(self.components, other.components)
)
def__mul__(self, scalar):
ifnotisinstance(scalar, (int, float)):
raise TypeError("标量必须是数值")
return ScientificVector(*(
x * scalar for x inself.components
))
def__matmul__(self, other):
"""点积运算 @"""
self._check_dim(other)
returnsum(x * y for x, y inzip(self.components, other.components))
def__abs__(self):
"""向量模长"""
returnsum(x**2for x inself.components)**0.5
def__round__(self, n=None):
"""四舍五入"""
return ScientificVector(*(
round(x, n) for x inself.components
))
def_check_dim(self, other):
ifself.dim != other.dim:
raise ValueError("向量维度不匹配")
def__str__(self):
returnf"Vector{self.components}"
def__repr__(self):
returnf"ScientificVector{self.components}"
# 使用
v1 = ScientificVector(1.2, 2.3, 3.4)
v2 = ScientificVector(0.8, 1.7, 2.6)
print(v1 + v2) # Vector(2.0, 4.0, 6.0)
print(v1 @ v2) # 1.2*0.8 + 2.3*1.7 + 3.4*2.6 = 14.91
print(abs(v1)) # sqrt(1.2^2 + 2.3^2 + 3.4^2) ≈ 4.28
print(round(v1, 1)) # Vector(1.2, 2.3, 3.4)
Python 的特殊方法是实现强大、灵活类设计的关键:
- 运算符重载:通过
__add__
, __sub__
等实现自然语法 - 行为定制:使用
__str__
, __len__
等定制对象行为 - 高级特性:利用
__call__
, __enter__
等实现高级模式 - 编程建议:保持一致性、避免常见陷阱、优化性能
一些要点:
- 上下文管理方法(
__enter__
/__exit__
)简化资源管理
"Python 的特殊方法是语言灵活性的核心体现。它们让开发者能够创建与内置类型无缝集成的自定义类型,使代码更加简洁、自然和表达力强。"
阅读原文:原文链接
该文章在 2025/7/18 10:35:21 编辑过