首页 / Python3 入门教程 / 定制类与魔术方法

Python3 入门教程

定制类与魔术方法

本教程共 70 篇 · 第 37 篇 · 更新于 2026-07-22 · 约 6 分钟阅读

PythonPython3 入门教程魔术方法__str____iter__运算符重载

37. 定制类与魔术方法

本节目标:掌握常用的「魔术方法(magic methods)」,让你的类能支持打印、迭代、索引、调用等 Pythonic 的操作。

Python 里有很多双下划线开头和结尾的方法,比如 __init____str__。它们叫「特殊方法(special methods)」,也叫「魔术方法」或「 dunder 方法(double under)」。

这些方法不需要你直接调用,Python 在特定场景下自动触发。比如 print(obj) 会自动调用 obj.__str__()for x in obj 会自动调用 obj.__iter__()

定制好这些方法,你的类就能像内置类型一样自然好用。

strrepr:对象的字符串表示

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
print(p)           # <__main__.Point object at 0x...>
print(repr(p))     # <__main__.Point object at 0x...>

默认的输出只有内存地址,没任何信息量。定制一下:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x!r}, {self.y!r})"

p = Point(1, 2)
print(p)           # Point(1, 2)
print(repr(p))     # Point(1, 2)

__str__ 是给用户看的,要友好可读。__repr__ 是给开发者看的,最好能通过 eval(repr(obj)) 重建对象。如果只有 __repr__ 没有 __str__print() 会回退到 __repr__

Tip

在交互式环境里直接输入变量名,显示的是 __repr__ 的结果。print() 调用的是 __str__。调试时 __repr__ 更重要,优先实现它。

len:支持 len()

想让 len() 对你的实例生效,实现 __len__

class Stack:
    def __init__(self):
        self._items = []

    def push(self, item):
        self._items.append(item)

    def __len__(self):
        return len(self._items)

s = Stack()
s.push(1)
s.push(2)
print(len(s))  # 2

iternext:让对象可迭代

想让对象支持 for 循环,需要实现 __iter__。如果还想支持手动迭代,再实现 __next__

class Countdown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        self.n = self.start
        return self

    def __next__(self):
        if self.n <= 0:
            raise StopIteration
        self.n -= 1
        return self.n + 1

for num in Countdown(5):
    print(num, end=' ')  # 5 4 3 2 1

__iter__ 返回迭代器对象自身(因为 Countdown 同时是「可迭代对象」和「迭代器」)。__next__ 每次返回下一个值,没有值了抛 StopIteration

Note

更常见的做法是让 __iter__ 返回一个生成器,代码更简洁:

def __iter__(self):
    n = self.start
    while n > 0:
        yield n
        n -= 1

getitem:支持索引和切片

实现 __getitem__,你的实例就能用 [] 访问:

class Fibonacci:
    def __init__(self, max_n):
        self.max_n = max_n
        self._cache = [0, 1]
        for i in range(2, max_n):
            self._cache.append(self._cache[-1] + self._cache[-2])

    def __getitem__(self, index):
        if isinstance(index, int):
            return self._cache[index]
        if isinstance(index, slice):
            return self._cache[index]
        raise TypeError("索引必须是整数或切片")

fib = Fibonacci(10)
print(fib[5])       # 5
print(fib[2:5])     # [1, 2, 3]

__getitem__ 的参数可以是整数、切片,甚至是自定义对象。这里我们只处理了整数和切片。

call:让实例像函数一样被调用

实现 __call__,你的实例就能加括号调用:

class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
triple = Multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15

这种写法在需要「带状态的函数」时很有用。比如一个计数器,每次调用都累加:

class Counter:
    def __init__(self):
        self.count = 0

    def __call__(self):
        self.count += 1
        return self.count

c = Counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3
Tip

判断一个对象是否可调用,用 callable(obj)。函数、类、实现了 __call__ 的实例,都返回 True

比较运算符:eqlt

想让实例支持 ==< 等比较,实现对应的魔术方法:

from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, major, minor, patch):
        self.major = major
        self.minor = minor
        self.patch = patch

    def __eq__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)

    def __lt__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

    def __repr__(self):
        return f"Version({self.major}, {self.minor}, {self.patch})"

v1 = Version(1, 2, 0)
v2 = Version(1, 3, 0)
print(v1 < v2)   # True
print(v1 == v2)  # False
print(v1 <= v2)  # True(由 @total_ordering 自动推导)

@total_ordering 是装饰器,你只要实现 __eq__ 和任意一个比较方法(__lt____le____gt____ge__),它会自动帮你补全剩下的。

Warning

比较方法里遇到不支持的类型,返回 NotImplemented,不要抛异常。这样 Python 会尝试让另一个对象来处理。

算术运算符:addmul

想让实例支持 +-* 等运算,实现对应方法:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

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

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)  # Vector(4, 6)

类似的还有 __sub__(减)、__mul__(乘)、__truediv__(除)等。完整列表可以查官方文档,日常用到的不多,但实现后能写出非常自然的数学代码。

小结

魔术方法触发场景示例
__str__print(obj)str(obj)用户友好的描述
__repr__交互式环境、repr(obj)开发者友好的描述
__len__len(obj)返回元素个数
__iter__for x in obj返回迭代器
__next__next(iterator)返回下一个值
__getitem__obj[key]obj[a:b]索引、切片访问
__call__obj()把实例当函数调用
__eq____lt__==< 等比较自定义比较逻辑
__add____mul__+* 等运算自定义算术行为

魔术方法不是炫技,而是让类的行为符合直觉。一个好的自定义类,用起来应该和 liststr 一样自然。


来源:参考了 runoob「Python3 面向对象」、liaoxuefeng「定制类」、pythondoc「9. 类」等,改写后所得。