Python 3.12 Magic Method – __divmod__(self, other)
__divmod__ 是 Python 中用于实现内置函数 divmod() 的魔术方法。divmod(a, b) 返回一个元组 (a // b, a % b),即同时获取地板除和取模的结果。通过实现 __divmod__,自定义类可以支持 divmod(),并保证与 // 和 % 运算符的行为一致。本文将详细解析其定义、底层机制、设计原则,并通过多个示例逐行演示如何正确实现。
1. 定义与签名
def __divmod__(self, other) –> tuple:
...
- 参数:
- self:当前对象(左操作数)。
- other:另一个操作数(右操作数),可以是任意类型。
- 返回值:必须返回一个二元元组 (quotient, remainder),其中 quotient 是 self // other 的结果,remainder 是 self % other 的结果。如果运算未定义(例如类型不兼容),应返回单例 NotImplemented。
- 调用时机:
- 调用 divmod(x, y) 时,首先尝试 x.__divmod__(y)。
- 如果返回 NotImplemented,则尝试 y.__rdivmod__(x)(反向 __divmod__)。
- 如果两者都返回 NotImplemented,最终抛出 TypeError。
2. 用途与典型场景
- 同时获取商和余数:在自定义数值类型中,一次性计算两个值,避免重复调用 // 和 %。
- 保证一致性:确保 // 和 % 运算与 divmod 返回的结果满足恒等式 a = b * q + r。
- 性能优化:某些数据类型可能可以高效地同时计算商和余数,比分别调用两次更快(例如大整数算法)。
- 数学对象:如多项式、矩阵、模运算等,可能定义自己的除法/取余操作。
- 与内置函数 divmod 无缝集成:让自定义类像内置类型一样自然地使用 divmod。
3. 底层实现机制
在 Python/C API 层面,每个类型对象(PyTypeObject)都有一个 tp_as_number 结构体,其中包含 nb_divmod 槽位。这是一个函数指针,用于处理 divmod 操作。当执行 divmod(x, y) 时,解释器会:
对于 Python 层定义的 __divmod__,它会被包装到 nb_divmod 槽位中。反向方法 __rdivmod__ 也会在必要时被调用。因此,实现 __divmod__ 时通常也应考虑实现 __rdivmod__ 以支持混合类型运算。
4. 设计原则与最佳实践
- 与 __floordiv__ 和 __mod__ 保持一致:__divmod__ 返回的元组必须等于 (self // other, self % other)。如果类实现了这两个运算符,应保证结果一致。
- 返回元组:必须返回一个包含两个元素的元组。如果返回其他类型,Python 会尝试将其转换为元组,但可能导致错误。
- 类型检查:应检查 other 的类型是否兼容,如果类型不匹配,应返回 NotImplemented,而不是抛出异常。这样给另一操作数提供尝试反向运算的机会。
- 处理除零异常:当 other 为 0 时,应抛出 ZeroDivisionError,与内置 divmod 一致。
- 返回新对象:商和余数通常应为新对象,不应修改原操作数(除非类是可变的)。
- 实现反向方法:为了支持 divmod(other, self) 的场景,应实现 __rdivmod__。
- 负数处理:遵循 Python 的取模规则(结果符号与除数一致),确保 // 和 % 满足恒等式。__divmod__ 应反映这一规则。
5. 示例与逐行解析
示例 1:简单的整数包装类(利用已有的 // 和 %)
class MyInt:
def __init__(self, value):
self.value = value
def __floordiv__(self, other):
if isinstance(other, MyInt):
return MyInt(self.value // other.value)
if isinstance(other, int):
return MyInt(self.value // other)
return NotImplemented
def __mod__(self, other):
if isinstance(other, MyInt):
return MyInt(self.value % other.value)
if isinstance(other, int):
return MyInt(self.value % other)
return NotImplemented
def __divmod__(self, other):
# 利用已定义的 // 和 % 获取结果,打包成元组返回
if isinstance(other, MyInt):
return (self // other, self % other) # 注意:self // other 调用 __floordiv__
if isinstance(other, int):
return (self // other, self % other)
return NotImplemented
def __repr__(self):
return f"MyInt({self.value})"
逐行解析:
| 1-3 | __init__ | 初始化整数值。 |
| 4-10 | __floordiv__ | 实现地板除,返回新 MyInt。 |
| 11-17 | __mod__ | 实现取模,返回新 MyInt。 |
| 18-24 | __divmod__ | 实现 divmod。 |
| 19-21 | 处理 MyInt 类型 | 调用已定义的 __floordiv__ 和 __mod__ 获取结果,直接使用 self // other 和 self % other(触发相应方法),打包成元组返回。 |
| 22-23 | 处理整数类型 | 同样调用相应方法,因为整数作为 other 时,// 和 % 也能工作(基于已实现的逻辑)。 |
| 24 | 返回 NotImplemented | 类型不支持时返回 NotImplemented,让 Python 尝试反向运算。 |
| 25-26 | __repr__ | 便于显示。 |
为什么这样写?
- 复用已实现的 // 和 % 方法,避免重复逻辑,保证一致性。
- 返回的元组包含两个 MyInt 对象,与 divmod 期望的返回值类型一致(内置 divmod 返回整数元组,这里是自定义类型元组)。
- 类型检查后返回 NotImplemented,让 Python 有机会尝试反向运算(如 divmod(10, a))。
验证:
a = MyInt(10)
b = MyInt(3)
q, r = divmod(a, b)
print(q) # MyInt(3)
print(r) # MyInt(1)
# 验证恒等式
print(a.value == b.value * q.value + r.value) # True
# 与整数混合
q2, r2 = divmod(a, 4)
print(q2) # MyInt(2)
print(r2) # MyInt(2)
运行结果:
MyInt(3)
MyInt(1)
True
MyInt(2)
MyInt(2)
示例 2:手动一次性计算(优化性能)
对于某些类型,可能可以更高效地同时计算商和余数,避免两次调用方法。
class MyInt:
def __init__(self, value):
self.value = value
def __divmod__(self, other):
if isinstance(other, MyInt):
q = self.value // other.value
r = self.value % other.value
return (MyInt(q), MyInt(r))
if isinstance(other, int):
q = self.value // other
r = self.value % other
return (MyInt(q), MyInt(r))
return NotImplemented
解析:
这里直接计算,没有调用 __floordiv__ 和 __mod__,可能更快。但注意要确保计算结果与单独调用 // 和 % 一致。
验证:
m1 = MyInt(10)
m2 = MyInt(5)
n1, n2 = divmod(m1, m2)
print(n1, n2)
print(divmod(m1, m2))
运行结果:
MyInt(2) MyInt(0)
(MyInt(2), MyInt(0))
示例 3:实现反向 __rdivmod__ 支持混合类型
当左操作数为内置类型(如 int)而右操作数为自定义类型时,需要实现反向方法。
class MyInt:
def __init__(self, value):
self.value = value
def __divmod__(self, other):
if isinstance(other, MyInt):
return (self // other, self % other)
return NotImplemented
def __rdivmod__(self, other):
# 处理 other (int) divmod self
if isinstance(other, int):
q = other // self.value
r = other % self.value
return (MyInt(q), MyInt(r))
return NotImplemented
def __floordiv__(self, other):
if isinstance(other, MyInt):
return MyInt(self.value // other.value)
return NotImplemented
def __mod__(self, other):
if isinstance(other, MyInt):
return MyInt(self.value % other.value)
return NotImplemented
def __repr__(self):
return f"MyInt({self.value})"
解析:
- divmod(10, a) 先尝试 10.__divmod__(a),但 int 没有 __divmod__,返回 NotImplemented。
- 然后尝试 a.__rdivmod__(10),成功返回结果。
- __rdivmod__ 中手动计算商和余数,返回 MyInt 对象,保持类型一致。
验证:
a = MyInt(3)
print(divmod(10, a)) # (MyInt(3), MyInt(1))
运行结果:
(MyInt(3), MyInt(1))
示例 4:处理负数,保证与 // 和 % 一致
Python 的 // 和 % 满足 a = b * (a // b) + a % b,且余数的符号与除数相同。实现 __divmod__ 时必须遵循这一规则。
class MyInt:
def __init__(self, value):
self.value = value
def __floordiv__(self, other):
return MyInt(self.value // other.value)
def __mod__(self, other):
return MyInt(self.value % other.value)
def __divmod__(self, other):
# 直接利用 Python 的内置整数运算,自动处理负数
q = self.value // other.value
r = self.value % other.value
return (MyInt(q), MyInt(r))
解析:
利用 Python 整数运算自动遵循取模规则,无需额外处理。
验证:
a = MyInt(–10)
b = MyInt(3)
q, r = divmod(a, b)
print(q) # MyInt(-4) 因为 -10 // 3 = -4
print(r) # MyInt(2) 因为 -10 % 3 = 2
运行结果:
MyInt(-4)
MyInt(2)
6. 注意事项与陷阱
- 不要修改 self:__divmod__ 应返回新对象,除非类是可变的且你明确希望就地修改(但通常不这样做)。
- 正确使用 NotImplemented:当类型不兼容时返回 NotImplemented,而不是抛出异常。这给另一侧机会处理。
- 与 __floordiv__ 和 __mod__ 的一致性:如果分别实现了这两个方法,__divmod__ 应返回与它们一致的结果。否则会导致不一致的行为,让用户困惑。
- 处理除零异常:当 other 为 0 时,应抛出 ZeroDivisionError,这与内置 divmod 一致。
- 返回类型:元组中的元素类型应与 __floordiv__ 和 __mod__ 返回的类型一致。例如,如果 // 返回 MyInt,% 返回 MyInt,那么 __divmod__ 也应返回 (MyInt, MyInt)。
- 负数处理:确保遵循 Python 的取模规则,否则会导致数学错误。
- 性能考虑:如果计算商和余数的方法可以一次完成(如大整数算法),应直接计算,避免两次调用。
7. 总结
| 角色 | 定义内置函数 divmod() 的行为 |
| 签名 | __divmod__(self, other) -> tuple |
| 返回值 | 二元元组 (quotient, remainder),或 NotImplemented |
| 调用时机 | divmod(x, y),以及反向尝试 |
| 底层 | C 层的 nb_divmod 槽位 |
| 与 __floordiv__/__mod__ 的关系 | 应保持一致,避免冗余计算 |
| 最佳实践 | 返回新对象、类型检查、使用 NotImplemented、处理除零、实现反向方法 |
掌握 __divmod__ 可以让自定义类支持 divmod 函数,并提供与 // 和 % 一致的语义。通过理解其底层机制和设计原则,你可以构建出更加完善和符合预期的数值类型。
如果在学习过程中遇到问题,欢迎在评论区留言讨论!

