前言
大家好,我是 倔强青铜三 。欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!
欢迎来到 苦练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 | |
__ror__ |
右按位或 | `3 | |
__ior__ |
原地按位或 | `a | |
__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__
python
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__
python
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__
(按位取反)
python
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__
python
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__
(反射)
python
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__
python
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 其他二元运算示例(乘法为例)
python
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__
python
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. 位运算魔术方法(位与为例)
python
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__
python
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. 实战:打造可运算的"秒表时间"类
python
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!
最后感谢阅读!欢迎关注我,微信公众号 :
倔强青铜三
。一键三连(点赞、收藏、关注)!