Python的魔术方法指类定义中以双下划线开头和结尾的特殊方法,也被称作dunder方法。它们不是用来直接调用的,而是由解释器在碰到特定语法或内置函数时自动触发。理解这套机制,是写出符合Python风格代码的关键。

什么是魔术方法
魔术方法本质上是Python数据模型的一部分。当你写下obj + other时,解释器会去查找obj.__add__(other);如果没找到,还会尝试反向方法other.__radd__(obj)。这种隐式调用让自定义对象可以像内建类型一样自然使用。
和普通方法不同,魔术方法通常不需要手动调用。比如初始化对象时写的__init__,是在ClassName()这个表达式执行时由解释器自动运行的。如果随意把__init__当普通函数反复调用,虽然不会报错,但往往说明类的设计存在问题。下面用一个简单类展示最基本的魔术方法:
class Book:
def __init__(self, title, price):
# 初始化实例属性
self.title = title
self.price = price
def __repr__(self):
# 供开发者查看的官方字符串表示
return f"Book(title={self.title!r}, price={self.price})"
def __str__(self):
# 供用户查看的友好字符串表示
return f"{self.title} 售价 {self.price} 元"
b = Book("Python基础", 59)
print(b) # 调用 __str__
print(repr(b)) # 调用 __repr__
常用实例化与表现类魔术方法
__new__和__init__经常一起出现,但分工不同。__new__负责真正创建实例对象,是一个静态方法;__init__在对象创建后做初始化赋值。多数情况下只需要重写__init__,只有在控制实例创建过程(如单例、不可变类型)时才碰__new__。
表现类方法主要影响对象如何被打印和转换。__repr__的目标是 unambiguous,通常应返回能重建对象的字符串;__str__则面向终端用户。如果只写了__repr__没写__str__,print()会退化使用__repr__。下面的例子演示二者差异:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __str__(self):
return f"坐标({self.x}, {self.y})"
p = Point(3, 4)
print(p) # 坐标(3, 4)
print(repr(p))# Point(x=3, y=4)
此外,__format__可定制格式化输出,__bytes__定义对象转字节逻辑。这些方法让对象能无缝接入字符串格式化、序列化等场景,而不必额外写转换函数。
运算符重载相关魔术方法
通过实现运算类魔术方法,自定义对象能支持加、减、比较等操作。例如__add__对应+,__eq__对应==,__lt__对应<。需要注意的是,运算符方法应当返回合理的新对象或布尔值,而不是偷偷修改自身,除非你明确实现就地运算符如__iadd__。
下面定义一个向量类,支持加法与相等比较。这里刻意处理了类型检查,避免和其他类型乱加导致诡异结果:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented # 告知解释器尝试反向或报错
def __eq__(self, other):
if isinstance(other, Vector):
return self.x == other.x and self.y == other.y
return NotImplemented
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 == v2) # False
当__add__返回NotImplemented且右操作数也没有合适方法时,Python才会抛出TypeError。这是一种优雅的协议设计,比直接抛异常更友好,也支持交叉类型运算。
上下文管理与容器类魔术方法
__enter__和__exit__让对象可用于with语句,典型如文件、数据库连接。进入时调用__enter__,退出时无论是否异常都会调用__exit__,它接收异常信息参数,返回真值可抑制异常。容器类方法如__len__、__getitem__、__setitem__则让对象表现得像列表或字典。
以下示例同时展示上下文管理与容器取值,模拟一个临时配置上下文:
class ConfigBox:
def __init__(self, data):
self._data = dict(data)
def __enter__(self):
# 进入上下文,可在此做备份
self._backup = self._data.copy()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 退出时恢复,忽略异常
self._data = self._backup
return False
def __getitem__(self, key):
return self._data.get(key)
def __setitem__(self, key, value):
self._data[key] = value
box = ConfigBox({"a": 1})
with box as b:
b["a"] = 99
print(b["a"]) # 99
print(box["a"]) # 1,已恢复
容器类方法若实现齐全,还能进一步支持迭代(__iter__)、成员判断(__contains__)。但应根据实际需求取舍,不必为了炫技把每个魔术方法都堆上去,否则会让类的职责变得模糊,也难以维护。
使用魔术方法的注意事项
第一,魔术方法名不要自己发明,只能使用Python数据模型里定义好的名称,否则解释器不会自动调用。第二,保持语义一致,比如__len__应返回非负整数,不应借机做副作用操作。第三,继承内置类型(如list)时,部分魔术方法可能走C层实现,行为略有差异,必要时用collections.UserList等纯Python基类。
最后,魔术方法虽强,但不是万能胶。若业务逻辑复杂,显式命名的方法往往比重载运算符更清晰。团队项目中,过度使用__getattr__之类动态拦截方法,会显著增加调试成本。合理边界内使用,才能既优雅又可控。
Python魔术方法dunder_methods修改时间:2026-08-02 03:45:32