前言
大家好,我是 倔强青铜三。欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!
欢迎来到 苦练Python第53天!
今天咱们杀进 面向对象 深水区——数值运算魔术方法。
当你写下 a + b、len(c)、abs(d) 这些看似简单的语句时,Python 背后其实偷偷调用了对象内部的 魔术方法。
一口气吃透这 20+ 个魔术方法,从此自定义对象也能像内置 int、float、complex 一样丝滑参与运算!
1. 为什么需要数值运算魔术方法?
- 让自定义类的实例支持
+ - * /等运算符 - 无缝接入内置函数
abs()round()divmod()等 - 提供与内置数值类型一致的交互体验
- 在科学计算、金融模型、游戏引擎里大放异彩
2. 魔术方法全景图速查表
| 类别 | 魔术方法 | 触发场景 | 对应表达式/函数 |
|---|---|---|---|
| 一元运算 | __neg__ | 负号 | -obj |
__pos__ | 正号 | +obj | |
__abs__ | 绝对值 | abs(obj) | |
__invert__ | 按位取反 | ~obj | |
__round__ | 四舍五入 | round(obj[, ndigits]) | |
__floor__ | 向下取整 | math.floor(obj) | |
__ceil__ | 向上取整 | math.ceil(obj) | |
__trunc__ | 截断取整 | math.trunc(obj) | |
| 二元运算 | __add__ | 加法 | a + b |
__radd__ | 右加法(反射) | 3 + a | |
__iadd__ | 原地加法 | a += b | |
__sub__ | 减法 | a - b | |
__rsub__ | 右减法 | 3 - a | |
__isub__ | 原地减法 | a -= b | |
__mul__ | 乘法 | a * b | |
__rmul__ | 右乘法 | 3 * a | |
__imul__ | 原地乘法 | a *= b | |
__truediv__ | 真除法 | a / b | |
__rtruediv__ | 右真除法 | 3 / a | |
__itruediv__ | 原地真除法 | a /= b | |
__floordiv__ | 地板除 | a // b | |
__rfloordiv__ | 右地板除 | 3 // a | |
__ifloordiv__ | 原地地板除 | a //= b | |
__mod__ | 取模 | a % b | |
__rmod__ | 右取模 | 3 % a | |
__imod__ | 原地取模 | a %= b | |
__pow__ | 幂运算 | a ** b 或 pow(a, b[, mod]) | |
__rpow__ | 右幂运算 | 3 ** a | |
__ipow__ | 原地幂运算 | a **= b | |
__divmod__ | 同时返回商和余 | divmod(a, b) | |
__rdivmod__ | 右divmod | divmod(3, a) | |
__lshift__ | 左移 | a << b | |
__rlshift__ | 右左移 | 3 << a | |
__ilshift__ | 原地左移 | a <<= b | |
__rshift__ | 右移 | a >> b | |
__rrshift__ | 右右移 | 3 >> a | |
__irshift__ | 原地右移 | a >>= b | |
__and__ | 按位与 | a & b | |
__rand__ | 右按位与 | 3 & a | |
__iand__ | 原地按位与 | a &= b | |
__or__ | 按位或 | a | b | |
__ror__ | 右按位或 | 3 | a | |
__ior__ | 原地按位或 | a |= b | |
__xor__ | 按位异或 | a ^ b | |
__rxor__ | 右按位异或 | 3 ^ a | |
__ixor__ | 原地按位异或 | a ^= b | |
| 类型转换 | __int__ | 转 int | int(obj) |
__float__ | 转 float | float(obj) | |
__complex__ | 转 complex | complex(obj) | |
__bool__ | 转 bool | bool(obj) | |
__index__ | 整数下标 | operator.index(obj)、切片 |
3. 一元运算魔术方法详解
3.1 __neg__ 与 __pos__
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __neg__(self):
"""触发场景:-v"""
print("__neg__ 被调用!")
return Vector2D(-self.x, -self.y)
def __pos__(self):
"""触发场景:+v"""
print("__pos__ 被调用!")
return Vector2D(self.x, self.y) # 通常返回自身或副本
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
# 触发示例
v = Vector2D(3, 4)
print(-v) # __neg__ 被调用!
print(+v) # __pos__ 被调用!
3.2 __abs__
import math
class Vector2D:
...
def __abs__(self):
"""触发场景:abs(v)"""
print("__abs__ 被调用!")
return math.hypot(self.x, self.y)
print(abs(v)) # __abs__ 被调用! -> 5.0
3.3 __invert__(按位取反)
class BitInt:
def __init__(self, value):
self.value = int(value)
def __invert__(self):
"""触发场景:~bit"""
print("__invert__ 被调用!")
return BitInt(~self.value)
def __repr__(self):
return str(self.value)
b = BitInt(5)
print(~b) # __invert__ 被调用! -> -6
3.4 取整三兄弟:__round__ / __floor__ / __ceil__ / __trunc__
import math
class ApproxFloat:
def __init__(self, value):
self.value = float(value)
def __round__(self, ndigits=None):
"""触发场景:round(obj[, nd])"""
print("__round__ 被调用!")
return round(self.value, ndigits)
def __floor__(self):
"""触发场景:math.floor(obj)"""
print("__floor__ 被调用!")
return math.floor(self.value)
def __ceil__(self):
"""触发场景:math.ceil(obj)"""
print("__ceil__ 被调用!")
return math.ceil(self.value)
def __trunc__(self):
"""触发场景:math.trunc(obj)"""
print("__trunc__ 被调用!")
return math.trunc(self.value)
af = ApproxFloat(3.14159)
print(round(af, 2)) # __round__ 被调用! -> 3.14
print(math.floor(af)) # __floor__ 被调用! -> 3
print(math.ceil(af)) # __ceil__ 被调用! -> 4
print(math.trunc(af)) # __trunc__ 被调用! -> 3
4. 二元运算魔术方法详解
下面以 加法 为例,其他运算符模式完全一样,只是名字不同。
4.1 __add__ 与 __radd__(反射)
class Dollar:
def __init__(self, amount):
self.amount = float(amount)
def __add__(self, other):
"""
触发场景:d1 + d2 或 d1 + 10
other 可能是 Dollar 或 int/float
"""
print("__add__ 被调用!")
if isinstance(other, Dollar):
return Dollar(self.amount + other.amount)
return Dollar(self.amount + other)
def __radd__(self, other):
"""
触发场景:10 + d1
仅在左操作数未实现加法或返回 NotImplemented 时触发
"""
print("__radd__ 被调用!")
return self + other # 复用 __add__
def __repr__(self):
return f"${self.amount:.2f}"
d1 = Dollar(10)
d2 = Dollar(5.5)
print(d1 + d2) # __add__ 被调用! -> $15.50
print(d1 + 3) # __add__ 被调用! -> $13.00
print(7 + d1) # __radd__ 被调用! -> $17.00
4.2 原地运算:__iadd__
class Dollar:
...
def __iadd__(self, other):
"""触发场景:d1 += d2 或 d1 += 10"""
print("__iadd__ 被调用!")
if isinstance(other, Dollar):
self.amount += other.amount
else:
self.amount += other
return self # 原地修改必须返回 self
d = Dollar(100)
d += Dollar(25)
print(d) # __iadd__ 被调用! -> $125.00
4.3 其他二元运算示例(乘法为例)
class SquareMatrix:
def __init__(self, n, data):
self.n = n
self.data = data # 一维列表按行展开
def __matmul__(self, other):
"""矩阵乘法 A @ B"""
# 这里故意简化,仅演示触发
print("__matmul__ 被调用!")
return self # 略去计算
# 触发示例
A = SquareMatrix(2, [1, 2, 3, 4])
B = SquareMatrix(2, [5, 6, 7, 8])
_ = A @ B # __matmul__ 被调用!
所有二元运算都具有
__op__/__rop__/__iop__三种形态,不再逐一举例。你可以把上面的__add__/__radd__/__iadd__模板直接套用到__sub__、__mul__、__truediv__、__floordiv__、__mod__、__pow__、__lshift__、__rshift__、__and__、__or__、__xor__。
4.4 __divmod__ 与 __rdivmod__
class Minute:
def __init__(self, total):
self.total = int(total)
def __divmod__(self, other):
"""触发场景:divmod(min, 60)"""
print("__divmod__ 被调用!")
hours, mins = divmod(self.total, int(other))
return Minute(hours), Minute(mins)
def __rdivmod__(self, other):
"""触发场景:divmod(120, min)"""
print("__rdivmod__ 被调用!")
return divmod(other, self.total)
def __repr__(self):
return f"{self.total}min"
m = Minute(135)
print(divmod(m, 60)) # __divmod__ 被调用! -> (2min, 15min)
print(divmod(120, m)) # __rdivmod__ 被调用! -> (0, 120)
5. 位运算魔术方法(位与为例)
class BitMask:
def __init__(self, value):
self.value = int(value) & 0xFF # 8bit
def __and__(self, other):
"""触发场景:bm1 & bm2 或 bm & 0x0F"""
print("__and__ 被调用!")
return BitMask(self.value & int(other))
def __rand__(self, other):
"""触发场景:0x0F & bm"""
print("__rand__ 被调用!")
return BitMask(int(other) & self.value)
def __repr__(self):
return f"0b{self.value:08b}"
bm1 = BitMask(0b11110000)
bm2 = BitMask(0b10101010)
print(bm1 & bm2) # __and__ 被调用! -> 0b10100000
print(0b00001111 & bm1) # __rand__ 被调用! -> 0b00000000
6. 类型转换魔术方法
6.1 __int__ / __float__ / __complex__
class Fraction:
def __init__(self, numerator, denominator):
self.num = numerator
self.den = denominator
def __int__(self):
"""触发场景:int(frac)"""
print("__int__ 被调用!")
return self.num // self.den
def __float__(self):
"""触发场景:float(frac)"""
print("__float__ 被调用!")
return self.num / self.den
def __complex__(self):
"""触发场景:complex(frac)"""
print("__complex__ 被调用!")
return complex(float(self), 0)
def __bool__(self):
"""触发场景:bool(frac)"""
print("__bool__ 被调用!")
return bool(self.num) # 0 为 False
def __index__(self):
"""触发场景:lst[frac]、bin(frac) 等"""
print("__index__ 被调用!")
return int(self)
f = Fraction(7, 2)
print(int(f)) # __int__ 被调用! -> 3
print(float(f)) # __float__ 被调用! -> 3.5
print(complex(f)) # __complex__ 被调用! -> (3.5+0j)
print(bool(f)) # __bool__ 被调用! -> True
lst = [10, 20, 30]
print(lst[f]) # __index__ 被调用! -> 30
7. 实战:打造可运算的“秒表时间”类
from functools import total_ordering
@total_ordering
class Stopwatch:
"""
支持 + - * / 比较运算、abs、int、float 的时间类
单位为秒,内部用 float 存储
"""
def __init__(self, seconds=0.0):
self.sec = float(seconds)
# 一元运算
def __neg__(self):
return Stopwatch(-self.sec)
def __abs__(self):
return Stopwatch(abs(self.sec))
# 二元运算
def __add__(self, other):
if isinstance(other, Stopwatch):
return Stopwatch(self.sec + other.sec)
return Stopwatch(self.sec + float(other))
__radd__ = __add__ # 加法可交换
def __sub__(self, other):
if isinstance(other, Stopwatch):
return Stopwatch(self.sec - other.sec)
return Stopwatch(self.sec - float(other))
def __rsub__(self, other):
return Stopwatch(float(other) - self.sec)
def __mul__(self, factor):
return Stopwatch(self.sec * float(factor))
__rmul__ = __mul__
def __truediv__(self, divisor):
return Stopwatch(self.sec / float(divisor))
# 比较运算
def __eq__(self, other):
return self.sec == float(other)
def __lt__(self, other):
return self.sec < float(other)
# 类型转换
def __int__(self):
return int(self.sec)
def __float__(self):
return self.sec
def __repr__(self):
return f"{self.sec:.2f}s"
# 触发示例
t1 = Stopwatch(120)
t2 = Stopwatch(45)
print(t1 + t2) # 165.00s
print(3 * t1) # 360.00s
print(abs(-t1)) # 120.00s
print(sorted([t2, t1, Stopwatch(90)])) # [45.00s, 90.00s, 120.00s]
8. 小结 & 思维导图
- 一元运算 让对象支持
- + abs ~ round floor ceil trunc - 二元运算 通过
__op__/__rop__/__iop__覆盖全部算术、位运算 - 类型转换 让对象可以
int()float()complex()bool()甚至切片 - 只要 方法返回值类型正确,Python 就能把你的对象当成原生数值一样用!
9. 动手挑战
-
实现一个
Vector3D类,支持向量加、减、点乘、叉乘、取模(abs)和打印。 -
写一个
Money类,支持+ - * /与浮点数交互,并且每次运算都保留 2 位小数。 -
进阶:让
Money支持原地运算+= -= *= /=,并且链式比较m1 < m2 <= m3。
完成后把代码贴到评论区,互相 Review!
最后感谢阅读!欢迎关注我,微信公众号:
倔强青铜三。
一键三连(点赞、收藏、关注)!