学习Python __pow__() 方法

793 阅读3分钟

句法

object.__pow__(self, other)

Python__pow__() 方法实现了内置的指数化操作。因此,当你调用 [pow(a, b)](https://blog.finxter.com/a-guide-to-pythons-pow-function/ "A Guide to Python’s pow() Function")a ** b 时,Python 会尝试调用x.__pow__(y) 。如果该方法没有实现,Python 首先尝试在右边的操作数上调用__rpow__ ,如果这个方法也没有实现,它就会引发一个TypeError

我们称这种方法为*"* Dunder Method",即*"Double UnderscoreMethod"(也叫"Magic Method")*。

背景 默认 pow()

双星号(**)符号被用作指数运算符。左边的操作数是基数,右边的操作数是幂。例如,表达式x**n 将数值x 与自身相乘,n 倍。

例子 自定义__pow__()

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

class Data:
        
    def __pow__(self, other):
        return '... my result of expoentiation...'


a = Data()
b = Data()

print(pow(a, b))
# ... my result of exponentiation...

print(a ** b)
# ... my result of exponentiation...

如果你没有定义__pow__() 方法,Python 会提出一个TypeError

TypeError: ** 或 pow() 的操作数类型不支持

考虑一下下面的代码片段,你试图在没有定义dunder方法的情况下计算两个自定义对象的指数__pow__()

class Data:
    pass


a = Data()
b = Data()

print(pow(a, b))
# ... my result of exponentiation...

print(a ** b)
# ... my result of exponentiation...

在我的电脑上运行这段代码会出现以下错误信息。

Traceback (most recent call last):
  File "C:\Users\xcent\Desktop\code.py", line 8, in <module>
    print(pow(a, b))
TypeError: unsupported operand type(s) for ** or pow(): 'Data' and 'Data'

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

class Data:
        
    def __pow__(self, other):
        return '... my result of expoentiation...'

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

Python __pow__模数

__pow__ 方法的第三个参数是mod 参数。如果存在的话,它计算基数(第一个参数)到指数(第二个参数)的幂,并对 第三个参数进行调制 。从语义上讲,__pow(x, y, mod)__ 计算(x ** y) % mod ,但是它的速度要快得多,因为模块化指数计算避免了计算x ** y 作为一个中间结果。

Python的__pow__与__rpow__比较

假设,你想计算两个自定义对象xy 的指数。

print(x ** y)

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

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

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

如果这个方法被实现了,Python 知道它没有遇到非交换性操作的潜在问题。如果它只是执行y.__pow__(x) 而不是x.__pow__(y) ,结果将是错误的,因为当自定义定义时,指数化操作可能是非交换性的。这就是为什么需要y.__rpow__(x)

因此,x.__pow__(y)x.__rpow__(y) 之间的区别是,前者计算x ** y ,而后者计算y ** x - 都是调用定义在对象x 上的各自的指数化方法。

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

class Data_1:
    pass

class Data_2:
    def __rpow__(self, other):
        return 'called exponentiation'


x = Data_1()
y = Data_2()

print(x ** y)
# called exponentiation