魔法方法
约 1824 字大约 6 分钟
2026-01-21
魔法方法(Magic Methods),也称为双下划线方法(Dunder Methods),是 Python 中一类具有特殊名称的方法,它们以双下划线开头和结尾(例如 init)。这些方法并非为了让用户直接调用,而是为了让 Python 解释器在特定情境下自动调用。
- 什么是魔法方法?
- 构造与初始化
- 字符串表示
- 运算符重载
- 1先把 __repr__ 写好(让对象在调试时显示可读信息),再考虑 __str__ —— 这是最该实现的两个魔法方法。
- 2实现 __eq__ 后记得也实现 __hash__;否则对象虽然可比较但不能放进 set 或 dict 的 key。
- 3实现 __len__ + __getitem__ —— Python 会自动让对象支持 for 迭代和切片,体会“鸭子接口”的威力。
- 4把上下文资源写成 __enter__/__exit__,就能用 with my_resource() as r 自动释放。
- 5@functools.total_ordering 让你只写 __eq__ + 一个序关系(如 __lt__),Python 自动补齐另外四个比较。
什么是魔法方法?
魔法方法(Magic Methods),也称为双下划线方法(Dunder Methods),是 Python 中一类具有特殊名称的方法,它们以双下划线开头和结尾(例如 __init__)。这些方法并非为了让用户直接调用,而是为了让 Python 解释器在特定情境下自动调用。
通过实现魔法方法,你可以自定义对象在内置操作(如加减运算、长度获取、索引访问、字符串表示等)中的行为。这使得你的自定义类可以像 Python 的内置类型一样自然地使用,这种特性被称为鸭子类型(Duck Typing)——如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。
构造与初始化
这是最常见的一组魔法方法,用于控制对象的创建和初始化过程。
__init__(self, ...)
初始化方法。当类的实例被创建后,Python 会立即调用此方法。这是初始化对象属性的最佳场所。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('Alice', 30)
print(p.name) # Alice__new__(cls, ...)
在 __init__ 之前调用的方法,负责创建并返回实例。__new__ 是一个静态方法(虽然不需要显式声明 @staticmethod),它接收类作为第一个参数(cls)。
通常情况下你不需要重写 __new__,除非你继承了不可变类型(如 tuple, str, int)并希望在创建时修改其行为,或者实现单例模式。
class PositiveInt(int):
def __new__(cls, value):
return super().__new__(cls, abs(value))
i = PositiveInt(-42)
print(i) # 42字符串表示
控制对象被打印或转换为字符串时的显示方式。
__str__(self)
定义对象的“非正式”或用户友好的字符串表示。当调用 str(obj) 或 print(obj) 时触发。
__repr__(self)
定义对象的“正式”字符串表示。目标是明确且无歧义,通常用于调试。理想情况下,repr 返回的字符串应该是有效的 Python 代码,可以被 eval() 执行以重新创建该对象。当你在交互式命令行直接输入变量名回车时,显示的就是 __repr__ 的结果。
最佳实践:至少实现
__repr__。如果__str__没有被实现,Python 会回退调用__repr__。
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(x={self.x}, y={self.y})'
p = Point(1, 2)
print(p) # 调用 __str__: Point(1, 2)
print(repr(p)) # 调用 __repr__: Point(x=1, y=2)运算符重载
魔法方法允许自定义对象支持算术和比较运算符。
比较运算符
| 方法 | 运算符 | 说明 |
|---|---|---|
__eq__(self, other) | == | 等于 |
__ne__(self, other) | != | 不等于 |
__lt__(self, other) | < | 小于 |
__gt__(self, other) | > | 大于 |
__le__(self, other) | <= | 小于等于 |
__ge__(self, other) | >= | 大于等于 |
示例:自定义数值比较
class Score:
def __init__(self, score):
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score
s1 = Score(90)
s2 = Score(95)
print(s1 < s2) # True
print(s1 == s2) # False算术运算符
| 方法 | 运算符 | 说明 |
|---|---|---|
__add__(self, other) | + | 加法 |
__sub__(self, other) | - | 减法 |
__mul__(self, other) | * | 乘法 |
__truediv__(self, other) | / | 真除法 |
__floordiv__(self, other) | // | 整除 |
示例:向量加法
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(2, 4)
v2 = Vector(1, -1)
print(v1 + v2) # Vector(3, 3)容器与序列模拟
想让对象像列表、元组或字典一样工作?实现这些方法:
__len__(self)
定义 len(obj) 的行为。
__getitem__(self, key)
定义获取元素 obj[key] 的行为。支持索引访问和切片。
__setitem__(self, key, value)
定义赋值 obj[key] = value 的行为。
__delitem__(self, key)
定义删除 del obj[key] 的行为。
__contains__(self, item)
定义成员检查 item in obj 的行为。
示例:用字典实现的自定义“牌组”
class Deck:
def __init__(self):
self._cards = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
def __contains__(self, item):
return item in self._cards
deck = Deck()
print(len(deck)) # 13
print(deck[0]) # A
print('Q' in deck) # True
# 由于实现了 __getitem__,它甚至自动支持切片和迭代!
print(deck[-3:]) # ['J', 'Q', 'K']
for card in deck:
print(card, end=' ') # A 2 3 ...可调用对象
__call__(self, ...)
允许对象像函数一样被调用。这在需要保存状态或将被调用的对象作为参数传递时非常有用。
class Repeater:
def __init__(self, times):
self.times = times
def __call__(self, message):
return (message + ' ') * self.times
repeat_3 = Repeater(3)
print(repeat_3('Hello')) # Hello Hello Hello上下文管理器
用于 with 语句中,确保资源的正确获取和释放。
__enter__(self)
进入上下文时调用,通常返回对象本身。
__exit__(self, exc_type, exc_value, traceback)
退出上下文时调用。如果有异常发生,可以通过返回 True 来抑制异常。
class OpenFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
# 使用示例
# with OpenFile('test.txt', 'w') as f:
# f.write('Hello via Magic Methods!')属性访问控制(高级)
__getattr__(self, name):当用户访问不存在的属性时调用。__getattribute__(self, name):当该类的属性被访问时,无条件调用(小心无限递归!)。__setattr__(self, name, value):当一个属性被赋值时调用。__delattr__(self, name):当一个属性被删除时调用。
总结
魔法方法是 Python 极其强大的特性,它赋予了用户自定义对象与内置类型同等的“特权”。掌握这些方法能让你写出更加 Pythonic、简洁且富有表现力的代码。但也要注意适度使用:如果一个方法的行为过于反直觉,也许使用普通的方法名会更好。
- 魔法方法是 Python 让自定义类“长得像内置类型”的接口;以 __xxx__ 命名,由解释器调。
- 最常用的:__init__ 初始化、__repr__/__str__ 显示、__eq__/__lt__ 比较、__len__/__getitem__ 容器化。
- 实现 __getitem__ 会自动获得迭代和切片能力;__call__ 让实例像函数一样被调用。
- 上下文管理器靠 __enter__/__exit__,是 with 的底层 —— 实现这两个就能自动管理资源。
- 魔法方法是双刃剑:用得好让 API 极简,用过头会让对象行为反直觉,慎用 __getattr__/__getattribute__ 等。
版权所有
版权归属:Shuo Liu
