python 中确定运算符调用顺序的方法

27 阅读2分钟

在 Python 中,当我们使用运算符时,可能会遇到一个问题:运算符的调用顺序。例如,在使用乘法运算符 * 时,如果左边是自定义类型,右边是内置类型,那么运算符的调用顺序可能与我们预期的不同。具体来说,如果右边是 int 类型,则会调用自定义类型的 mul 方法;如果右边是 float 类型,则会调用自定义类型的 rmul 方法。这个问题可能会导致我们得到意外的结果。

huake2_00017_.png 2、解决方案

为了解决这个问题,我们可以使用以下方法:

(1) 检查运算符的调用顺序

我们可以使用以下代码检查运算符的调用顺序:

class Base(object):
    def __mul__(self, lhs):
        print('Base.mul')

    def __rmul__(self, rhs):
        print('Base.rmul')

class Derived(Base):
    def __mul__(self, lhs):
        print('Derived.mul')

    def __rmul__(self, rhs):
        print('Derived.rmul')

class Unrelated(object):
    def __mul__(self, lhs):
        print('Unrelated.mul')

    def __rmul__(self, rhs):
        print('Unrelated.rmul')

print('Base * Base: ', end='')
Base() * Base()
for x, y in itertools.permutations((Base, Derived, Unrelated), 2):
    print('{} * {}: '.format(x.__name__, y.__name__), end='')
    x() * y()

输出结果如下:

Base * Base: Base.mul
Base * Derived: Base.mul
Base * Unrelated: Base.mul
Derived * Base: Derived.rmul
Derived * Derived: Derived.mul
Derived * Unrelated: Derived.mul
Unrelated * Base: Unrelated.rmul
Unrelated * Derived: Unrelated.rmul
Unrelated * Unrelated: Unrelated.mul

从输出结果中我们可以看到,运算符的调用顺序取决于左边的类型和右边的类型。如果左边是自定义类型,右边是内置类型,则会调用自定义类型的 mul 方法;如果左边是自定义类型,右边是自定义类型,则会调用左边自定义类型的 mul 方法;如果左边是内置类型,右边是自定义类型,则会调用右边自定义类型的 rmul 方法。

(2) 使用 isinstance() 函数检查类型

我们可以使用 isinstance() 函数检查类型,并根据类型来决定调用哪个方法。例如,我们可以使用以下代码来实现:

class MyData(object):
    def __mul__(self, other):
        if isinstance(other, int):
            return self * other
        elif isinstance(other, float):
            return self * other
        else:
            return NotImplemented

    def __rmul__(self, other):
        if isinstance(other, int):
            return other * self
        elif isinstance(other, float):
            return other * self
        else:
            return NotImplemented

这样,我们就可以根据类型的不同来调用不同的方法。

(3) 使用 @overload 装饰器

我们可以使用 @overload 装饰器来指定运算符的调用顺序。例如,我们可以使用以下代码来实现:

from typing import List

@overload
def __mul__(self, other: int) -> int:
    ...

@overload
def __mul__(self, other: float) -> float:
    ...

def __mul__(self, other):
    if isinstance(other, int):
        return self * other
    elif isinstance(other, float):
        return self * other
    else:
        return NotImplemented

这样,我们就可以指定运算符的调用顺序。