自定义数值魔术方法

PythonPythonBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

在本教程中,我们将介绍与数值操作相关的 Python 魔术方法(magic methods)。魔术方法是 Python 类中以双下划线(__)开头和结尾的特殊方法,也被称为 "dunder" 方法(双下划线)。

这些魔术方法允许你定义类的实例在特定操作(如加法或减法)中的行为。

我们将涵盖以下部分:

  1. 一元运算符
  2. 二元运算符
  3. 就地操作

让我们开始吧!


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python/BasicConceptsGroup -.-> python/numeric_types("Numeric Types") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") subgraph Lab Skills python/numeric_types -.-> lab-7838{{"自定义数值魔术方法"}} python/classes_objects -.-> lab-7838{{"自定义数值魔术方法"}} end

一元运算符

一元操作是涉及单个操作数的操作,例如取反、绝对值等。

让我们从一个简单的对象开始。在 number.py 中,创建一个名为 MyNumber 的类,该类具有一个属性 value

class MyNumber:
    def __init__(self, value: float):
        self.value = value

__neg__

__neg__ 魔术方法定义了取反操作的行为。当你对类的实例使用 - 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __neg__(self) -> 'MyNumber':
        """返回实例值的取反结果。"""
        return MyNumber(-self.value)

__abs__

__abs__ 魔术方法定义了绝对值操作的行为。当你对类的实例使用 abs() 函数时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __abs__(self) -> 'MyNumber':
        """返回实例值的绝对值。"""
        return MyNumber(abs(self.value))

__round__

__round__ 魔术方法定义了四舍五入操作的行为。当你对类的实例使用 round() 函数时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __round__(self, ndigits: int = None) -> 'MyNumber':
        """将实例值四舍五入到最接近的整数或指定的小数位数。"""
        return MyNumber(round(self.value, ndigits))

__floor__

__floor__ 魔术方法定义了向下取整操作的行为。当你对类的实例使用 math.floor() 函数时,会调用此方法。

## 需要在 number.py 的顶部导入 math 模块
import math

    ## ... (number.py 中的先前代码)

    def __floor__(self) -> 'MyNumber':
        """返回小于或等于实例值的最大整数。"""
        return MyNumber(math.floor(self.value))

__ceil__

__ceil__ 魔术方法定义了向上取整操作的行为。当你对类的实例使用 math.ceil() 函数时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __ceil__(self) -> 'MyNumber':
        """返回大于或等于实例值的最小整数。"""
        return MyNumber(math.ceil(self.value))

示例:使用一元运算符

现在我们已经为 MyNumber 类定义了一元运算符,让我们在 unary_example.py 中看看它们是如何工作的:

import math
from number import MyNumber

## 创建一个新的 MyNumber 对象
a = MyNumber(5)
## 使用 __neg__ 方法并打印结果
print(f'{a.value=}, {-a.value=}')  ## 输出: a.value=5, -a.value=-5

## 创建另一个新的 MyNumber 对象
a = MyNumber(-5)
## 使用 __abs__ 方法并打印结果
print(f'{a.value=}, {abs(a).value=}')  ## 输出: a.value=-5, abs(a).value=5

## 创建第三个新的 MyNumber 对象
a = MyNumber(5.678)
## 使用 __round__ 方法并打印结果
print(f'{a.value=}, {round(a, 2).value=}')  ## 输出: a.value=5.678, round(a, 2).value=5.68

## 使用 __floor__ 方法并打印结果
print(f'{a.value=}, {math.floor(a).value=}')  ## 输出: a.value=5.678, math.floor(a).value=5

## 使用 __ceil__ 方法并打印结果
print(f'{a.value=}, {math.ceil(a).value=}')  ## 输出: a.value=5.678, math.ceil(a).value=6

然后在终端中输入以下命令来执行脚本。

python unary_example.py

二元运算符

二元操作是涉及两个操作数的操作,例如加法、减法、乘法和除法等算术运算,以及等于、不等于、小于、大于等比较运算。

__add__

__add__ 魔术方法定义了加法操作的行为。当你对类的实例使用 + 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __add__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值与其他实例值的和。"""
        return MyNumber(self.value + other.value)

__sub__

__sub__ 魔术方法定义了减法操作的行为。当你对类的实例使用 - 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __sub__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值与其他实例值的差。"""
        return MyNumber(self.value - other.value)

__mul__

__mul__ 魔术方法定义了乘法操作的行为。当你对类的实例使用 * 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __mul__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值与其他实例值的乘积。"""
        return MyNumber(self.value * other.value)

__truediv__

__truediv__ 魔术方法定义了真除法操作的行为。当你对类的实例使用 / 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __truediv__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值除以其他实例值的结果。"""
        return MyNumber(self.value / other.value)

__floordiv__

__floordiv__ 魔术方法定义了地板除法操作的行为。当你对类的实例使用 // 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __floordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值除以其他实例值的结果的最大整数部分。"""
        return MyNumber(self.value // other.value)

__mod__

__mod__ 魔术方法定义了取模操作的行为。当你对类的实例使用 % 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __mod__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值除以其他实例值的余数。"""
        return MyNumber(self.value % other.value)

__pow__

__pow__ 魔术方法定义了幂运算操作的行为。当你对类的实例使用 ** 运算符或 pow() 函数时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __pow__(self, other: 'MyNumber') -> 'MyNumber':
        """返回实例值的其他实例值次幂。"""
        return MyNumber(self.value ** other.value)

示例:使用二元运算符

现在我们已经为 MyNumber 类定义了二元运算符,让我们在 binary_example.py 中看看它们是如何工作的:

from number import MyNumber

## 创建两个新的 MyNumber 对象
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## 输出: a.value=5, b.value=3

## 使用 __add__ 方法并打印结果
print(f'{(a+b).value=}')  ## 输出: (a+b).value=8

## 使用 __sub__ 方法并打印结果
print(f'{(a-b).value=}')  ## 输出: (a-b).value=2

## 使用 __mul__ 方法并打印结果
print(f'{(a*b).value=}')  ## 输出: (a*b).value=15

## 使用 __truediv__ 方法并打印结果
print(f'{(a/b).value=}')  ## 输出: (a/b).value=1.6666666666666667

## 使用 __floordiv__ 方法并打印结果
print(f'{(a//b).value=}')  ## 输出: (a//b).value=1

## 使用 __mod__ 方法并打印结果
print(f'{(a%b).value=}')  ## 输出: (a%b).value=2

## 使用 __pow__ 方法并打印结果
print(f'{(a**b).value=}')  ## 输出: (a**b).value=125

然后在终端中输入以下命令来执行脚本。

python binary_example.py

就地操作

就地操作是直接修改对象值而不创建新对象的操作,通常通过增强赋值运算符表示,例如 +=-=*=/= 等。

如果 Python 类未定义就地操作符,则在尝试执行就地操作时,将使用二元操作符代替。

inplace_example1.py 中有一个示例,将二元操作符改为就地操作符:

from number import MyNumber

## 创建两个新的 MyNumber 对象
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## 输出: a.value=5, b.value=3

a += b
## 使用 __add__ 方法并打印结果
print(f'after a+=b: {a.value=}')  ## 输出:after a+=b: (a+b).value=8

要运行此示例,请在终端中输入以下命令:

python inplace_example1.py

结果显示,在尝试执行 += 操作时,使用了 __add__ 方法。

接下来,我们将在 MyNumber 中实现就地操作,它们的行为与对应的二元操作符略有不同。

__iadd__

__iadd__ 魔术方法定义了就地加法操作的行为。当你对类的实例使用 += 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __iadd__(self, other: 'MyNumber') -> 'MyNumber':
        """将其他实例的值加到当前实例的值上(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value += other.value
        print(f'after +=: {self.value=}')
        return self

__isub__

__isub__ 魔术方法定义了就地减法操作的行为。当你对类的实例使用 -= 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __isub__(self, other: 'MyNumber') -> 'MyNumber':
        """从当前实例的值中减去其他实例的值(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value -= other.value
        print(f'after -=: {self.value=}')
        return self

__imul__

__imul__ 魔术方法定义了就地乘法操作的行为。当你对类的实例使用 *= 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __imul__(self, other: 'MyNumber') -> 'MyNumber':
        """将当前实例的值乘以其他实例的值(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value *= other.value
        print(f'after *=: {self.value=}')
        return self

__itruediv__

__itruediv__ 魔术方法定义了就地真除法操作的行为。当你对类的实例使用 /= 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __itruediv__(self, other: 'MyNumber') -> 'MyNumber':
        """将当前实例的值除以其他实例的值(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value /= other.value
        print(f'after /=: {self.value=}')
        return self

__ifloordiv__

__ifloordiv__ 魔术方法定义了就地地板除法操作的行为。当你对类的实例使用 //= 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __ifloordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """将当前实例的值除以其他实例的值并取整(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value //= other.value
        print(f'after //=: {self.value=}')
        return self

__imod__

__imod__ 魔术方法定义了就地取模操作的行为。当你对类的实例使用 %= 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __imod__(self, other: 'MyNumber') -> 'MyNumber':
        """将当前实例的值对其他实例的值取模(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value %= other.value
        print(f'after %=: {self.value=}')
        return self

__ipow__

__ipow__ 魔术方法定义了就地幂运算操作的行为。当你对类的实例使用 **= 运算符时,会调用此方法。

    ## ... (number.py 中的先前代码)

    def __ipow__(self, other: 'MyNumber') -> 'MyNumber':
        """将当前实例的值提升为其他实例值的幂(就地操作)。"""
        print(f'input: {self.value=}, {other.value=}')
        self.value **= other.value
        print(f'after **=: {self.value=}')
        return self

示例:使用就地操作

现在我们已经为 MyNumber 类定义了就地操作符,让我们在 inplace_example2.py 中看看它们是如何工作的:

from number import MyNumber

## 创建一个新的 MyNumber 对象
a = MyNumber(13)

## 使用 __iadd__ 方法
a += MyNumber(5)
## 输出:
## input: self.value=13, other.value=5
## after +=: self.value=18

## 使用 __isub__ 方法
a -= MyNumber(5)
## 输出:
## input: self.value=18, other.value=5
## after -=: self.value=13

## 使用 __imul__ 方法
a *= MyNumber(5)
## 输出:
## input: self.value=13, other.value=5
## after *=: self.value=65

## 使用 __itruediv__ 方法
a /= MyNumber(5)
## 输出:
## input: self.value=65, other.value=5
## after /=: self.value=13.0

## 使用 __ifloordiv__ 方法
a //= MyNumber(2)
## 输出:
## input: self.value=13.0, other.value=2
## after //=: self.value=6.0

## 使用 __imod__ 方法
a %= MyNumber(4)
## 输出:
## input: self.value=6.0, other.value=4
## after %=: self.value=2.0

## 使用 __ipow__ 方法
a **= MyNumber(3)
## 输出:
## input: self.value=2.0, other.value=3
## after **=: self.value=8.0

然后在终端中输入以下命令来执行脚本。

python inplace_example2.py

总结

在本教程中,我们探讨了与数值操作相关的 Python 魔术方法(magic methods),这些方法允许你为类定义与不同类型数值操作交互时的自定义行为。我们涵盖了一元运算符、二元运算符和就地操作,并学习了如何实现每个魔术方法。

通过在自定义类中实现这些魔术方法,你可以创建直观且易于使用的对象,这些对象能够与标准的 Python 操作无缝协作。这不仅提高了代码的可读性,还使其更易于维护和用户友好。

随着你继续提升 Python 技能,可以尝试使用其他魔术方法,进一步自定义类的行为,并创建更强大的抽象。