Python 运算符与函数重载:让自定义类支持内置操作

2026-10-03 40 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:5 分钟

Python 的自定义类并不只能保存数据。通过实现一组特殊方法,也就是常说的 dunder methods,可以让对象参与加法、比较、迭代、长度计算,甚至被内置函数直接处理。掌握这些方法,是理解 Python 数据模型和面向对象编程的重要一步。

运算符背后的特殊方法

当代码写成 a + b 时,Python 会尝试调用 a.__add__(b)。类似地,a == b 通常对应 __eq__,a < b 对应 __lt__,len(a) 对应 __len__。

下面的例子实现了一个二维向量类:

from __future__ import annotations

from math import hypot


class Vector:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

    def __add__(self, other: Vector) -> Vector:
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __abs__(self) -> float:
        return hypot(self.x, self.y)

    def __repr__(self) -> str:
        return f"Vector({self.x}, {self.y})"


first = Vector(3, 4)
second = Vector(1, 2)

print(first + second)       # Vector(4, 6)
print(first == Vector(3, 4)) # True
print(abs(first))           # 5.0

返回 NotImplemented 比直接返回 False 或抛出无关异常更合适。它告诉 Python 当前对象无法处理这个操作,解释器仍有机会尝试右操作数的反向方法,例如 __radd__。

让对象支持内置函数

函数重载在 Python 中通常不是通过同名函数写多个参数版本实现的。更常见的做法是实现协议方法,让内置函数知道如何使用对象。

例如,len(value) 会寻找 value.__len__(),str(value) 会寻找 value.__str__(),而 list(value) 会尝试从对象获取迭代器。

class Playlist:
    def __init__(self, songs: list[str]) -> None:
        self._songs = list(songs)

    def __len__(self) -> int:
        return len(self._songs)

    def __iter__(self):
        return iter(self._songs)

    def __contains__(self, song: str) -> bool:
        return song in self._songs

    def __getitem__(self, index: int) -> str:
        return self._songs[index]

    def __repr__(self) -> str:
        return f"Playlist({self._songs!r})"


playlist = Playlist(["Intro", "Theme", "Finale"])
print(len(playlist))           # 3
print("Theme" in playlist)     # True
print(playlist[0])             # Intro
print(list(playlist))          # ['Intro', 'Theme', 'Finale']

这种设计的价值在于,调用方可以使用熟悉的 Python 语法,而不必记住一组专门的 API 名称。不过,协议方法应保持直观语义:__len__ 应返回数量,__contains__ 应表达成员关系,__iter__ 应返回可迭代对象。

实践时要注意什么

运算符重载不是把所有操作都塞进类里的理由。可以根据对象的自然含义选择少量方法:

  • 用 __add__ 表达有明确业务意义的合并或相加。
  • 用 __eq__ 定义值相等,而不是默认的对象身份相等。
  • 用 __lt__、__le__ 等支持排序时,确保比较规则稳定。
  • 用 __repr__ 提供便于调试的表达式风格文本。
  • 对不支持的操作返回 NotImplemented,让 Python 按协议继续处理。
  • 注意 __eq__ 与 __hash__ 的关系:可变对象不应轻易实现可哈希行为。

一个好的自定义类,应该让 obj + other、len(obj) 或 item in obj 看起来像自然的 Python 代码。如果某个重载需要用户阅读大量说明才能理解,普通方法可能更清晰。

小结

运算符和函数重载的核心不是记住一长串特殊方法,而是理解 Python 数据模型:语法和内置函数会通过约定好的方法与对象交互。先明确对象的语义,再实现最小的协议集合,并用不支持的类型和边界情况测试它们,通常能得到更可靠、也更符合 Python 风格的类。


相关推荐