定制类与魔术方法
本教程共 70 篇 · 第 37 篇 · 更新于 2026-07-22 · 约 6 分钟阅读
37. 定制类与魔术方法
本节目标:掌握常用的「魔术方法(magic methods)」,让你的类能支持打印、迭代、索引、调用等 Pythonic 的操作。
Python 里有很多双下划线开头和结尾的方法,比如 __init__、__str__。它们叫「特殊方法(special methods)」,也叫「魔术方法」或「 dunder 方法(double under)」。
这些方法不需要你直接调用,Python 在特定场景下自动触发。比如 print(obj) 会自动调用 obj.__str__(),for x in obj 会自动调用 obj.__iter__()。
定制好这些方法,你的类就能像内置类型一样自然好用。
str 与 repr:对象的字符串表示
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
iter 与 next:让对象可迭代
想让对象支持 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。
比较运算符:eq、lt 等
想让实例支持 ==、< 等比较,实现对应的魔术方法:
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 会尝试让另一个对象来处理。
算术运算符:add、mul 等
想让实例支持 +、-、* 等运算,实现对应方法:
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__ | +、* 等运算 | 自定义算术行为 |
魔术方法不是炫技,而是让类的行为符合直觉。一个好的自定义类,用起来应该和 list、str 一样自然。
来源:参考了 runoob「Python3 面向对象」、liaoxuefeng「定制类」、pythondoc「9. 类」等,改写后所得。