学习Python __divmod__() 方法

342 阅读3分钟

语法

object.__divmod__(self, other)

Python的__divmod__() 方法实现了内置的divmod操作。因此,当你调用divmod(a, b) 时,Python 会尝试调用x.__divmod__(y) 。如果这个方法没有实现,Python首先尝试在右边的操作数上调用__rdivmod__ ,如果这个方法也没有实现,就会引发一个TypeError

我们把这称为 "Dunder Method",即*"Double UnderscoreMethod"(也称为"Magic Method")*。

背景 默认divmod()

Python 内置的divmod(a, b) 函数接收两个整数或浮点数ab 作为输入参数,并返回一个 tuple(a // b, a % b) 。第一个元组的值是 整数除法的结果a//b 。第二个元组是余数的结果,也叫模数运算 a % b 。如果是浮动输入,divmod() 仍然通过向下舍入到下一个整数来返回没有余数的除法。

要想详细了解这个操作,请随时阅读我们的教程或观看以下视频。

自定义divmod()示例

在下面的例子中,你创建了一个自定义类Data ,并重写了__divmod__() 方法,以便在试图计算两个数字的模数时返回一个假字符串。

class Data:
        
    def __divmod__(self, other):
        return '... my result of divmod...'


a = Data()
b = Data()
c = divmod(a, b)

print(c)
# ... my result of divmod...

如果你没有定义__divmod__() 方法,Python 会产生一个TypeError

如何解决类型错误:divmod()的操作数类型不支持

考虑下面的代码片段,你试图在没有定义dunder方法的情况下分割两个自定义对象__truediv__()

class Data:
    pass


a = Data()
b = Data()
c = divmod(a, b)

print(c)

在我的电脑上运行这个代码会导致以下错误信息。

Traceback (most recent call last):
  File "C:\Users\xcent\Desktop\code.py", line 7, in <module>
    c = divmod(a, b)
TypeError: unsupported operand type(s) for divmod(): 'Data' and 'Data'

这个错误的原因是,__divmod__() 方法从来没有被定义过--而且默认情况下,它没有为自定义对象定义。所以,为了解决TypeError: unsupported operand type(s) for divmod() ,你需要在你的类定义中提供__divmod__(self, other) 方法,如前所示。

class Data:
        
    def __divmod__(self, other):
        return '... my result of divmod...'

当然,在实践中你会使用另一个返回值,正如在**"Background divmod() "**部分所解释的。

Python __divmod__ vs __rdivmod__

假设,你想计算两个自定义对象的divmodxy

print(divmod(x, y))

Python 首先尝试调用左边对象的__divmod__() 方法x.__divmod__(y) 。但这可能因为两个原因而失败。

  1. 方法x.__divmod__() 首先没有实现,或者
  2. 方法x.__divmod__() 已经实现,但是返回一个NotImplemented 值,表明数据类型不兼容。

如果失败了,Python 试图通过调用y.__rdivmod__() 对右操作数进行反向 divmod来解决这个问题y

如果这个方法被实现,Python 知道它没有遇到非交换性操作的潜在问题。如果它只是执行y.__divmod__(x) 而不是x.__divmod__(y) ,结果将是错误的,因为 divmod 操作是非交换性的 (无论是整数除法,还是模数操作都不是交换性的)。这就是为什么需要y.__rdivmod__(x)

因此,x.__divmod__(y)x.__rdivmod__(y) 之间的区别是,前者计算(x // y, x % y) 而后者计算(y // x, y % x) - 两者都调用定义在对象x 上的各自的 divmod 方法。

你可以在这里看到这个效果,我们试图在左边的操作数x 上调用 divmod 操作 - 但是由于它没有实现,Python 只是在右边的操作数y 上调用反向的 divmod 操作。

class Data_1:
    pass

class Data_2:
    def __rdivmod__(self, other):
        return 'called divmod'


x = Data_1()
y = Data_2()

print(divmod(x, y))
# called divmod