一、开篇:让+、==对你的对象也有效
当你写3 + 5时,python实际调用的是(3).__add__(5)。当你写"hello" == "world"时,调用的是"hello".__eq__("world")。python的每一个运算符背后都有一个魔法方法——你可以为自己的类实现这些方法,让+、-、==、<这些运算符对你的对象生效。
先看最直观的例子:
class vector:
"""二维向量——支持加减"""
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""v1 + v2"""
return vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""v1 - v2"""
return vector(self.x - other.x, self.y - other.y)
def __repr__(self):
return f"vector({self.x}, {self.y})"
v1 = vector(3, 4)
v2 = vector(1, 2)
print(v1 + v2) # vector(4, 6)
print(v1 - v2) # vector(2, 2)
二、算术运算符
2.1 双目运算符
class money:
"""金额类——支持算术运算"""
def __init__(self, amount, currency="cny"):
self.amount = amount
self.currency = currency
def __add__(self, other):
if self.currency != other.currency:
raise valueerror("不同货币不能直接相加")
return money(self.amount + other.amount, self.currency)
def __sub__(self, other):
if self.currency != other.currency:
raise valueerror("不同货币不能直接相减")
return money(self.amount - other.amount, self.currency)
def __mul__(self, factor):
"""金额 × 数量"""
if isinstance(factor, (int, float)):
return money(self.amount * factor, self.currency)
return notimplemented # 不支持的操作返回notimplemented
def __truediv__(self, divisor):
"""金额 ÷ 数量"""
if divisor == 0:
raise zerodivisionerror("除数不能为0")
return money(self.amount / divisor, self.currency)
def __floordiv__(self, divisor):
"""金额 // 数量"""
return money(self.amount // divisor, self.currency)
def __repr__(self):
return f"money({self.amount:.2f}, {self.currency})"
# 使用
price = money(100)
quantity = 3
total = price * quantity
print(total) # money(300.00, cny)
print(total / 2) # money(150.00, cny)
print(money(500) - money(200)) # money(300.00, cny)
2.2 反向运算符和增强赋值
class vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return vector(self.x + other.x, self.y + other.y)
def __radd__(self, other):
"""反向加法:当左操作数不支持加法时调用"""
# 例如:tuple + vector
if isinstance(other, (tuple, list)) and len(other) == 2:
return vector(self.x + other[0], self.y + other[1])
return notimplemented
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})"
v = vector(3, 4)
# __radd__的例子
# result = (1, 2) + v # 元组不支持 + vector,python调用v.__radd__((1,2))
# print(result) # vector(4, 6)
# __iadd__的例子
v += vector(1, 1)
print(v) # vector(4, 5) —— v自身被修改了
三、比较运算符
3.1 等于和不等于
class person:
def __init__(self, name, id_number):
self.name = name
self.id_number = id_number
def __eq__(self, other):
"""== —— 通过身份证号判断是否同一个人"""
if not isinstance(other, person):
return notimplemented
return self.id_number == other.id_number
def __ne__(self, other):
"""!= —— 通常直接反转__eq__即可"""
result = self.__eq__(other)
if result is notimplemented:
return result
return not result
def __hash__(self):
"""定义了__eq__就必须定义__hash__(用于set和dict的键)"""
return hash(self.id_number)
def __repr__(self):
return f"person({self.name}, {self.id_number})"
p1 = person("张三", "110101199001011234")
p2 = person("张三别名", "110101199001011234") # 同一身份证号
p3 = person("李四", "110101199501011234")
print(p1 == p2) # true —— 同一个人!
print(p1 == p3) # false
print(p1 != p3) # true
# 可以放入集合——根据__hash__和__eq__去重
people = {p1, p2, p3}
print(len(people)) # 2 —— p1和p2被视为同一个
3.2 大小比较
from functools import total_ordering
@total_ordering # 只需定义__eq__和__lt__,其余自动生成
class version:
"""语义化版本号"""
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __eq__(self, other):
return (self.major, self.minor, self.patch) == \
(other.major, other.minor, other.patch)
def __lt__(self, other):
return (self.major, self.minor, self.patch) < \
(other.major, other.minor, other.patch)
def __repr__(self):
return f"v{self.major}.{self.minor}.{self.patch}"
versions = [version(2, 0, 1), version(1, 5, 3),
version(2, 1, 0), version(1, 0, 0)]
print(sorted(versions))
# [v1.0.0, v1.5.3, v2.0.1, v2.1.0]
# @total_ordering自动生成了:__le__, __gt__, __ge__
四、容器类运算符
4.1getitem、setitem、len
class matrix:
"""简单的二维矩阵——支持[][]索引"""
def __init__(self, rows, cols, default=0):
self.rows = rows
self.cols = cols
self._data = [[default] * cols for _ in range(rows)]
def __getitem__(self, index):
"""matrix[row][col]"""
return self._data[index]
def __setitem__(self, index, value):
"""matrix[row] = [val1, val2, ...]"""
if len(value) != self.cols:
raise valueerror(f"每行必须有{self.cols}个元素")
self._data[index] = list(value)
def __len__(self):
"""len(matrix) → 行数"""
return self.rows
def __contains__(self, value):
"""value in matrix —— 检查矩阵中是否有某个值"""
return any(value in row for row in self._data)
def __repr__(self):
return "\n".join(" ".join(f"{v:3d}" for v in row) for row in self._data)
# 使用
m = matrix(3, 4)
m[0] = [1, 2, 3, 4]
m[1] = [5, 6, 7, 8]
m[2] = [9, 10, 11, 12]
print(m[0][2]) # 3
print(f"行数: {len(m)}") # 3
print(7 in m) # true
4.2 完整运算符速查表
# 算术运算符 # __add__(+), __sub__(-), __mul__(*), __truediv__(/), __floordiv__(//) # __mod__(%), __pow__(**), __divmod__() # __radd__, __rsub__, ... (反向运算符) # __iadd__, __isub__, ... (增强赋值 += 等) # 比较运算符 # __eq__(==), __ne__(!=), __lt__(<), __le__(<=), __gt__(>), __ge__(>=) # 一元运算符 # __neg__(-), __pos__(+), __abs__(abs()), __invert__(~) # 容器运算符 # __len__(len), __getitem__([]), __setitem__([]=), __delitem__(del []) # __contains__(in), __iter__(for) # 类型转换 # __int__(int), __float__(float), __bool__(bool), __str__(str), __bytes__(bytes) # 其他 # __call__(obj()), __enter__/__exit__(with), __hash__(hash)
五、总结
运算符重载让你的对象用起来像内置类型一样自然。但不要滥用——只在你真的需要、且行为符合直觉时才重载。
核心要点:
- 运算符背后是魔法方法——
+→__add__,==→__eq__ __eq__和__hash__要一起定义——否则set/dict出问题@total_ordering——定义__eq__和__lt__就自动生成全部比较方法- 反向运算用
__radd__等——当左操作数不支持时被调用 - 不支持的操作返回
notimplemented——让python尝试对方的反向方法
原则:运算符的行为应该"符合直觉"。+永远是加法或拼接,==永远是比较相等——不要搞创新。
到此这篇关于python面向对象之__add__、__eq__运算符重载方法详解的文章就介绍到这了,更多相关python运算符重载内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论