深入掌握 Python 面向对象的灵魂——魔法函数(Magic / Dunder Methods)全景指南
目录
一、为什么是“魔法”
二、魔法函数的分类总览
三、对象生命周期
四、对象描述
五、属性管理
六、容器协议
七、可调用对象与 with 语句
八、数值/位运算
九、比较与排序
十、异步魔法函数(Python 3.5+)
十一、结语
一、为什么是“魔法”
在 Python 里,所有以双下划线开头、又以双下划线结尾的标识符,都被解释器预留为特殊用途。它们数量众多、各司其职,却又能让自定义类无缝融入 Python 的原生语法与执行模型。因为它们的存在,len(obj) 可以工作,for i in obj 可以遍历,with obj 可以自动管理资源,甚至连 +、[]、== 这些看似“内置”的运算,都能被用户类重新定义。于是,社区把这种“看似黑箱、实则透明”的机制称为 Magic Methods,中文通常叫“魔法函数”或“魔术方法”。
二、魔法函数的分类总览
官方文档把它们散布在 data-model 章节,初学者常感零散。若按功能归纳,可分为七大类:
-
对象生命周期:创建、初始化、析构
-
对象描述:字符串表示、字节表示、哈希、布尔值
-
属性管理:点号访问、描述符协议
-
容器协议:长度、取值、赋值、删除、迭代
-
可调用与上下文:把对象当函数、进入/退出 with
-
数值/位运算:一元、二元、原地、反向运算
-
比较与富比较:==、>、<、排序键等
下面逐类展开,给出最小可运行示例与工程化建议。
三、对象生命周期
1.new(cls, *args, **kw)
负责“生”,是真正的构造器;必须返回一个实例对象。
class Singleton:_inst = Nonedef __new__(cls, *a, **k):if cls._inst is None:cls._inst = super().__new__(cls)return cls._inst
注意:new 常用于不可变类型子类化(如 tuple、int)或控制实例创建策略;日常业务代码极少需要重写。
2.init(self, …)
负责“养”,在 new 返回的实例上初始化属性。若父类有 init,记得 super().init()。
3.del(self)
当对象的引用计数归零、即将被回收时调用。由于 Python 的垃圾回收策略不确定,del 里不要访问全局资源,也不要依赖执行时机。更安全的做法是实现上下文管理器(见第 5 节)。
四、对象描述
1.字符串表示
repr(self):给开发者看的“官方”字符串,理想状态下 eval(repr(obj))==obj。
str(self):给最终用户看的友好文本。
format(self, spec):支持 f'{obj:spec}'。
bytes(self):定义 bytes(obj)。
2.布尔值与哈希
bool(self):控制 bool(obj)。若未定义,则退回到 len;若都无,默认真。
hash(self):让对象可放入 set/dict;必须与 eq 保持一致性:a==b ⇒ hash(a)==hash(b)。
五、属性管理
1.点号访问三兄弟
getattr(self, name):访问不存在的属性时触发。
setattr(self, name, value):任何属性赋值都会触发,因此内部赋值需用 super().
setattr 或 self.
dict 避免递归。
delattr(self, name):删除属性。
2.描述符协议(descriptor protocol)
get(self, obj, owner) / set / delete
这是实现 @property、staticmethod、classmethod 的底层机制。写 ORM 字段、类型校验库时常用。
示例:自定义验证字段
class Range:def __init__(self, min_, max_):self.min, self.max = min_, max_def __set_name__(self, owner, name):self.attr = namedef __set__(self, obj, value):if not self.min <= value <= self.max:raise ValueError(value)obj.__dict__[self.attr] = valuedef __get__(self, obj, owner):return obj.__dict__[self.attr]class Person:age = Range(0, 120)
六、容器协议
要让自定义类表现得像列表、字典,只需实现以下若干魔法函数:
-
len(self) → 支持 len(obj)
-
getitem(self, key) → obj[key];key 可为整数或 slice 对象
-
setitem(self, key, value) → obj[key]=value
-
delitem(self, key) → del obj[key]
-
iter(self) → iter(obj)
-
reversed(self) → reversed(obj)
-
contains(self, item) → item in obj
示例:实现一个只读平方表
class SquareTable:def __init__(self, n):self._n = ndef __len__(self): return self._ndef __getitem__(self, idx):if 0 <= idx < self._n:return idx * idxraise IndexErrordef __iter__(self):for i in range(self._n):yield i * i
实现了上述函数后,for x in SquareTable(5)、list(SquareTable(3))、2 in SquareTable(10) 都能直接工作,这就是协议的力量。
七、可调用对象与 with 语句
1.call(self, *a, **k)
把实例当函数用,常用于策略模式、缓存装饰器、闭包替代。
class Counter:def __init__(self):self.n = 0def __call__(self):self.n += 1return self.n
c = Counter()
print(c(), c()) # 1 2
2.上下文管理器协议
-
enter(self) → 返回值赋给 as 变量
-
exit(self, exc_type, exc, tb) → 清理资源,返回 True 可吞掉异常
class temp_chdir:def __init__(self, path):self.path = pathself.origin = Nonedef __enter__(self):import osself.origin = os.getcwd()os.chdir(self.path)return self.pathdef __exit__(self, *args):import osos.chdir(self.origin)with temp_chdir('/tmp'):open('test.txt', 'w').write('hello')
标准库 contextlib.contextmanager 也依赖此协议。
八、数值/位运算
Python 将运算符全部映射到魔法函数,例如:
-
→ add
-
→ sub
-
→ mul
@ → matmul
/ → truediv
// → floordiv
% → mod
** → pow
& → and
| → or
^ → xor
<< → lshift
→ rshift
每个运算符又有“正向”、“反向”、“就地”三个版本:
正向:add(self, other)
反向:radd(self, other)
就地:iadd(self, other)
反向版本用于解决 int + Vector 时,int 无法识别 Vector 的场景;就地版本则用于 += 运算符,若未定义则退化为正向运算后赋值。
示例:二维向量
class V2:def __init__(self, x, y):self.x, self.y = x, ydef __repr__(self): return f'V2({self.x}, {self.y})'def __add__(self, other):if isinstance(other, V2):return V2(self.x + other.x, self.y + other.y)return NotImplementeddef __radd__(self, other): # 交换律可复用return self + otherdef __iadd__(self, other):if isinstance(other, V2):self.x += other.xself.y += other.yreturn selfreturn NotImplemented
返回 NotImplemented 能触发解释器尝试 other 的反向运算,保持可扩展性。
九、比较与排序
-
eq、ne、lt、le、gt、ge
-
hash(若对象不可变,通常同时实现,以便作为 dict 键)
-
bool(见第四节)
functools.total_ordering 装饰器能根据 eq 与任一排序魔法函数自动生成剩余方法,减少样板代码。
十、异步魔法函数(Python 3.5+)
-
await:定义 await obj
-
aiter、anext:异步迭代器
-
aenter、aexit:异步上下文管理器
示例:异步计数器
class AsyncCounter:def __init__(self, n):self.i = 0self.n = ndef __aiter__(self):return selfasync def __anext__(self):if self.i >= self.n:raise StopAsyncIterationawait asyncio.sleep(0.1)self.i += 1return self.i
十一、结语
魔法函数不是炫技,而是 Python 设计哲学的浓缩:
“不强迫继承,只要求协议。”
当你熟练掌握了 getitem、iter、enter、add 等双下方法,就能写出“像内置一样自然”的类库:Requests 让网络请求像字典,Pydantic 让数据校验像 dataclass,FastAPI 让 Web 路由像函数。它们背后,正是魔法函数在默默工作。
希望本文的梳理与示例,能让你在面向对象的道路上,真正拥有“让代码说话”的魔法力量。