什么是 Python 中的魔术方法以及如何使用它们

Python 中一项相对不为人知,但极其宝贵的特性是能够在对象上实现魔术方法。通过运用魔术方法,我们可以编写出更加简洁、直观且易于理解的代码。

借助魔术方法,我们能够创建接口,从而以更符合 Python 风格的方式与对象互动。本文将向您详细介绍魔术方法,探讨创建它们的最佳实践,并深入研究您可能会遇到的常见魔术方法。

什么是魔术方法?

魔术方法是 Python 中一类特殊的方法,它们定义了 Python 对象在执行常规操作时的行为方式。这些方法的名称前后都带有双下划线,从而明确地加以区分。

因此,它们通常被称作 “dunder 方法”,即双下划线方法。您可能已经接触过一个常见的 dunder 方法,那就是用于定义类构造函数的 __init__() 方法。

一般来说,dunder 方法不应该在您的代码中直接调用;相反,它们会在程序运行时由解释器自动调用。

为什么魔术方法有用?

魔术方法是 Python 面向对象编程中一个非常实用的概念。通过它们,您可以自定义数据类型在使用常见的内置操作时的行为。这些操作包括:

🟢 算术运算

🟢 比较操作

🟢 生命周期操作

🟢 表示操作

下一节将详细讨论如何实现魔术方法,从而定义应用程序在上述所有类别中的行为方式。

如何定义魔术方法

如前所述,魔术方法用来指定对象的行为。因此,它们被定义为对象类的一部分。由于它们是对象类的一部分,所以它们以 self 作为第一个参数,这是对对象自身的引用。

它们可以接受额外的参数,具体取决于解释器如何调用它们。 它们的名称前后都带有两个下划线,这是它们被明确定义的标志。

实践

到目前为止,我们所讨论的内容似乎都偏理论和抽象。在本节中,我们将实践操作,实现一个简单的 Rectangle(矩形)类。

这个类将具有长度和宽度属性。通过使用 __init__ 方法,您可以在实例化时指定这些属性。此外,您可以使用 ==、< 和 > 运算符比较不同的矩形,以判断它们是否相等、小于或大于另一个矩形。最后,矩形应该能够提供有意义的字符串表示。

设置编码环境

要完成本演示,您将需要一个 Python 运行时环境。您可以使用本地环境,也可以使用在线的 techblik.com Python 编译器。

创建矩形类

首先,让我们从定义 Rectangle 类开始。

    class Rectangle:
        pass
    

创建构造方法

接下来,让我们创建我们的第一个魔术方法,类构造函数方法。此方法将获取高度和宽度,并将它们作为属性存储在类实例中。

    class Rectangle:
        def __init__(self, height, width):
            self.height = height
            self.width = width
    

为字符串表示创建魔术方法

接下来,我们将创建一个方法,允许我们的类生成一个人类可读的字符串来表示对象。每当我们调用 str() 函数,并将 Rectangle 类的实例作为参数传入时,都会调用此方法。当您调用需要字符串参数的函数(例如打印函数)时,也会调用此方法。

    class Rectangle:
        def __init__(self, height, width):
            self.height = height
            self.width = width

        def __str__(self):
            return f'Rectangle({self.height}, {self.width})'
    

__str__() 方法应该返回一个您想要用来表示对象的字符串。在本例中,我们将返回格式为 “Rectangle(, )” 的字符串,其中高度和宽度是矩形的存储尺寸。

为比较操作创建魔术方法

接下来,我们要为等于、小于和大于运算创建比较运算符。这称为运算符重载。为了创建这些运算符,我们分别使用魔术方法 __eq__、__lt__ 和 __gt__。这些方法将在比较矩形的面积后返回一个布尔值。

    class Rectangle:
        def __init__(self, height, width):
            self.height = height
            self.width = width

        def __str__(self):
            return f'Rectangle({self.height}, {self.width})'

        def __eq__(self, other):
            """ Checking for equality """
            return self.height * self.width == other.height * other.width

        def __lt__(self, other):
            """ Checking if the rectangle is less than the other one """
            return self.height * self.width < other.height * other.width

        def __gt__(self, other):
            """ Checking if the rectange is greater than the other one """
            return self.height * self.width > other.height * other.width
    

如您所见,这些方法有两个参数。第一个是当前矩形,第二个是与它进行比较的另一个值。该值可以是另一个 Rectangle 实例,也可以是任何其他值。比较的逻辑以及比较返回真值的条件完全取决于您。

常用魔术方法

在下一节中,我们将探讨您可能会遇到和使用的常见魔术方法。

#1. 算术运算

当您的类的一个实例被放置在算术符号的左侧时,会调用算术魔术方法。该方法将使用两个参数进行调用,第一个是对实例的引用。第二个值是符号右侧的对象。方法及其对应符号如下:

名称 方法 符号 描述
加法 __add__ + 实现加法。
减法 __sub__ 实现减法。
乘法 __mul__ * 实现乘法。
除法 __div__ / 实现除法。
整除 __floordiv__ // 实现整除。

#2. 比较操作

与算术魔术方法类似,当定义它们的类的实例位于比较运算符的左侧时,将调用这些方法。此外,与算术魔术方法一样,它们使用两个参数进行调用;第一个是对对象实例的引用。第二个是对符号右侧值的引用。

名称 方法 符号 描述
小于 __lt__ < 实现小于比较。
大于 __gt__ > 实现大于比较。
等于 __eq__ == 实现等于比较。
小于等于 __le__ <= 实现小于等于比较。
大于等于 __ge__ >= 实现大于等于比较。

#3. 生命周期操作

这些方法将在对象的不同生命周期阶段被调用,例如被实例化或被删除。构造函数 __init__ 就是属于此类的方法。此类别中的常用方法如下表所示:

名称 方法 描述
构造函数 __init__ 每当创建为其定义的类的对象时调用此方法。
析构函数 __del__ 每当删除为其定义的类的对象时调用此方法。它可用于执行清理操作,例如关闭已打开的任何文件。
新建 __new__ __new__ 方法在指定类的对象被实例化时最先调用。此方法在构造函数之前调用,并接受类以及任何其他参数。它返回类的一个实例。在大多数情况下,它不是很有用,但这里会详细介绍。

#4. 表示操作

名称 方法 描述
字符串表示 __str__ 返回对象的人类可读字符串表示形式。当您调用 str() 函数,并将类的实例作为参数传递时,将调用此方法。当您将实例传递给 print() 和 format() 函数时,也会调用此方法。它旨在提供应用程序的最终用户可以理解的字符串。
对象表示 __repr__ 返回开发人员使用的对象的字符串表示形式。理想情况下,返回的字符串应该包含丰富的信息,这样您就可以仅从该字符串构造一个相同的对象实例。

创建魔术方法的最佳实践

魔术方法功能强大,并且可以简化您的代码。但是,在使用它们时请务必牢记以下几点。

  • 谨慎使用它们——在您的类中实现过多的魔术方法可能会使您的代码难以理解。限制自己只实现基本的方法。
  • 在使用 __setattr__ 和 __getattr__ 等方法之前,请务必了解它们对性能的影响。
  • 记录您的魔术方法的行为,以便其他开发人员能够清楚地了解它们的功能。这使得他们更容易使用这些方法并在必要时进行调试。

最后的话

在本文中,我介绍了魔术方法,一种创建可与内置操作一起使用的类的方法。我还探讨了它们的定义方式,并通过一个实现魔术方法的类的示例进行了说明。接下来,在分享一些需要注意的最佳实践之前,我列举了您可能会使用和需要的不同方法。

接下来,您可能想了解如何在 Python 中实现 Counter 类。