Python 中的魔法函数(Magic Methods),又称特殊方法(Special Methods)或双下方法(Dunder Methods,其名称以双下划线__ 开头和结尾),是一组预定义的方法名,用于让自定义类能够模拟 Python 内置类型的行为,并支持语言的核心语法(如运算符、属性访问、函数调用等)。通过实现这些方法,可以使自定义对象表现得像内置对象一样自然,从而充分利用 Python 的灵活性和一致性。
本文将全面、详细地介绍 Python 中常用的魔法函数,按功能分类讲解每个方法的用途、参数、返回值、调用时机以及实际代码示例。
1. 对象生命周期与初始化
| |
|---|
__new__(cls[, ...]) | 创建并返回一个新实例。在 __init__ 之前调用,是真正的构造函数(通常用于不可变类型或控制实例创建)。 |
__init__(self[, ...]) | 初始化已创建的实例。在 __new__ 之后调用,负责设置对象的初始状态。 |
__del__(self) | 析构函数,当对象被垃圾回收时调用(注意:不保证立即调用,除非明确 del 且引用计数归零)。 |
示例:
classSingleton: _instance = Nonedef__new__(cls, *args, **kwargs):ifnot cls._instance: cls._instance = super().__new__(cls)return cls._instancedef__init__(self, value): self.value = valuea = Singleton(10)b = Singleton(20)print(a is b) # Trueprint(a.value, b.value)
20 20(因为共享同一个实例,__init__ 被每次调用覆盖)
2. 对象字符串表示
| |
|---|
__repr__(self) | 返回对象的“官方”字符串表示,通常应能被 eval() 重建该对象(或给出清晰信息)。由 repr() 调用,也是 print() 回退时使用。 |
__str__(self) | 返回对象的“非正式”字符串表示,对用户友好。由 str() 和 print() 调用。未定义时回退到 __repr__。 |
__format__(self, format_spec) | 由 format() 和 f-string 调用,实现自定义格式化。 |
__bytes__(self) | |
示例:
classPoint:def__init__(self, x, y): self.x = x self.y = ydef__repr__(self):return f"Point({self.x}, {self.y})"def__str__(self):return f"({self.x}, {self.y})"def__format__(self, spec):if spec == 'polar':import math r = math.hypot(self.x, self.y) theta = math.atan2(self.y, self.x)returnf"(r={r:.2f}, θ={theta:.2f})"return str(self)p = Point(3, 4)print(repr(p)) print(str(p)) print(f"{p:polar}")
(r=5.00, θ=0.93)
(3, 4)
Point(3, 4)
3. 比较与哈希
| |
|---|
__lt__(self, other) | |
__le__(self, other) | |
__eq__(self, other) | |
__ne__(self, other) | |
__gt__(self, other) | |
__ge__(self, other) | |
__hash__(self) | 由 hash() 调用,返回整数哈希值。如果对象是可哈希的(如用作字典键),需要实现此方法,通常与 __eq__ 一致。 |
注意: 如果定义了 __eq__ 而没有定义 __hash__,则实例会变成不可哈希的。如果希望实例可变,通常不定义 __hash__ 或设为 None。
示例:
classPerson:def__init__(self, name, age): self.name = name self.age = agedef__eq__(self, other):ifnot isinstance(other, Person):returnNotImplementedreturn self.name == other.name and self.age == other.agedef__hash__(self):return hash((self.name, self.age))def__lt__(self, other):ifnot isinstance(other, Person):returnNotImplementedreturn self.age < other.agep1 = Person("Alice", 30)p2 = Person("Alice", 30)p3 = Person("Bob", 25)print(p1 == p2) # Trueprint(p1 < p3) # Falseprint(hash(p1) == hash(p2)) # True
4. 属性访问控制
| |
|---|
__getattr__(self, name) | |
__getattribute__(self, name) | 所有属性访问都会先经过此方法(除非有显式优化)。需谨慎使用,避免无限递归。 |
__setattr__(self, name, value) | |
__delattr__(self, name) | |
__dir__(self) | |
示例:
classDynamicProxy:def__init__(self, obj): self._obj = objdef__getattr__(self, name): print(f"Delegating {name}")return getattr(self._obj, name)def__setattr__(self, name, value):if name == "_obj": super().__setattr__(name, value)else: setattr(self._obj, name, value)classData: x = 10d = Data()proxy = DynamicProxy(d)print(proxy.x) # 输出 Delegating x 然后 10proxy.y = 20print(d.y) # 20
5. 容器与序列模拟
| |
|---|
__len__(self) | |
__getitem__(self, key) | |
__setitem__(self, key, value) | |
__delitem__(self, key) | |
__contains__(self, item) | 支持 item in self。未定义时使用 __iter__ 遍历查找。 |
__iter__(self) | |
__next__(self) | 迭代器的下一个元素,由 next() 调用(迭代器类需实现)。 |
__reversed__(self) | |
__missing__(self, key) | 当在字典子类中 __getitem__ 找不到键时调用(仅用于 dict 子类)。 |
示例(不可变序列):
classRange:def__init__(self, start, stop, step=1): self.start = start self.stop = stop self.step = stepdef__len__(self):return max(0, (self.stop - self.start + self.step - 1) // self.step)def__getitem__(self, index):if index < 0: index += len(self)if0 <= index < len(self):return self.start + index * self.stepraise IndexError("index out of range")r = Range(1, 10, 2)print(len(r)) # 5print(r[2]) # 5print(5in r) # True(因为定义了 __getitem__ 和 __len__,Python 可以自动处理迭代和成员测试)for v in r: print(v) # 1 3 5 7 9
6. 数值运算魔法函数
Python 允许通过魔法函数重载所有运算符。以下分类列举:
6.1 一元运算符
| |
|---|
__neg__(self) | |
__pos__(self) | |
__abs__(self) | |
__invert__(self) | |
__round__(self, n) | round(obj, n) |
__floor__(self) | math.floor(obj) |
__ceil__(self) | math.ceil(obj) |
__trunc__(self) | math.trunc(obj) |
6.2 二元算术运算符
| |
|---|
__add__(self, other) | |
__sub__(self, other) | |
__mul__(self, other) | |
__matmul__(self, other) | |
__truediv__(self, other) | |
__floordiv__(self, other) | |
__mod__(self, other) | |
__divmod__(self, other) | divmod(obj, other) |
__pow__(self, other[, modulo]) | |
__lshift__(self, other) | |
__rshift__(self, other) | |
__and__(self, other) | |
__xor__(self, other) | |
__or__(self, other) | |
6.3 反射(反向)算术运算符
当左操作数不支持相应操作时,Python 会尝试右操作数的反射方法,形式为 __r*__(如 __radd__)。这些方法参数相同,用于实现交换律。
6.4 增量赋值运算符
形式为 __i*__(如 __iadd__),用于 +=、-= 等。如果未定义,Python 会退化为普通运算符赋值(a = a + b),但效率较低且可能改变对象身份。
6.5 类型转换
| |
|---|
__int__(self) | |
__float__(self) | |
__complex__(self) | |
__bool__(self) | 转换为布尔值,由 bool() 调用(未定义时返回 len(self) != 0 或 True) |
__index__(self) | 转换为整数用于切片索引等(必须返回整数),由 operator.index() 调用 |
完整示例(复数向量类):
import mathclassVector:def__init__(self, x, y): self.x = x self.y = ydef__repr__(self):return f"Vector({self.x}, {self.y})"# 加法def__add__(self, other):if isinstance(other, Vector):return Vector(self.x + other.x, self.y + other.y)returnNotImplemented# 反射加法(确保 vector + number 可以交换)def__radd__(self, other):return self.__add__(other) # 如果 other 是标量,可自行处理# 绝对值:模长def__abs__(self):return math.hypot(self.x, self.y)# 布尔值:模长非零def__bool__(self):return bool(abs(self))# 支持向量乘以标量def__mul__(self, scalar):if isinstance(scalar, (int, float)):return Vector(self.x * scalar, self.y * scalar)return NotImplemented __rmul__ = __mul__ # 交换律v1 = Vector(3, 4)v2 = Vector(1, 2)print(v1 + v2) # Vector(4, 6)print(2 * v1) # Vector(6, 8)print(abs(v1)) # 5.0print(bool(v1)) # True
7. 可调用对象
示例:
classAdder:def__init__(self, n): self.n = ndef__call__(self, x):return self.n + xadd_five = Adder(5)print(add_five(3)) # 8print(callable(add_five)) # True
8. 上下文管理器
| |
|---|
__enter__(self) | 进入 with 块时调用,返回值绑定到 as 后的变量。 |
__exit__(self, exc_type, exc_value, traceback) | 退出 with 块时调用,负责清理资源,返回 True 表示抑制异常。 |
示例:
classManagedFile:def__init__(self, filename, mode): self.filename = filename self.mode = modedef__enter__(self): self.file = open(self.filename, self.mode)return self.filedef__exit__(self, exc_type, exc_val, exc_tb):if self.file: self.file.close()if exc_type: print(f"Exception occurred: {exc_val}")return True# 抑制异常(通常不这样做,除非特殊需求)with ManagedFile('test.txt', 'w') as f: f.write('hello')# 文件自动关闭,即使写操作异常也会处理
9. 描述符协议
描述符是实现了特定方法的类,用于管理另一个类的属性访问。常用在 @property、staticmethod、classmethod 等机制中。
| |
|---|
__get__(self, instance, owner) | 获取属性值。instance 是拥有该属性的实例,owner 是拥有者类。 |
__set__(self, instance, value) | |
__delete__(self, instance) | |
__set_name__(self, owner, name) | Python 3.6+,在类创建时被调用,告知描述符在类中的属性名。 |
示例:
classPositiveNumber:def__set_name__(self, owner, name): self.name = namedef__get__(self, instance, owner):if instance isNone:return selfreturn instance.__dict__.get(self.name, 0)def__set__(self, instance, value):if value < 0:raise ValueError("Value must be positive") instance.__dict__[self.name] = valueclassOrder: quantity = PositiveNumber() price = PositiveNumber()def__init__(self, quantity, price): self.quantity = quantity self.price = pricedeftotal(self):return self.quantity * self.priceorder = Order(10, 5)print(order.total()) # 50# order.quantity = -10 # ValueError
10. 其他常用魔法函数
| |
|---|
__instancecheck__(self, instance) | 自定义 isinstance() 行为(用于元类)。 |
__subclasscheck__(self, subclass) | 自定义 issubclass() 行为(用于元类)。 |
__class__ | |
__slots__ | |
__init_subclass__(cls, **kwargs) | Python 3.6+,当子类被创建时调用,用于注册或修改子类。 |
__set_name__ | |
__dir__ | |
__sizeof__(self) | 返回对象的内存大小(字节),由 sys.getsizeof() 调用。 |
__copy__(self) 和 __deepcopy__(self, memo) | |
__reduce__(self) 和 __reduce_ex__(self, protocol) | |
示例(__slots__):
classPoint: __slots__ = ('x', 'y')def__init__(self, x, y): self.x = x self.y = yp = Point(1, 2)# p.z = 3 # AttributeError: 'Point' object has no attribute 'z'print(p.__slots__) # ('x', 'y')
示例(__init_subclass__):
classPluginBase: plugins = []def__init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls.plugins.append(cls)classPluginA(PluginBase):passclassPluginB(PluginBase):passprint(PluginBase.plugins) # [<class '__main__.PluginA'>, <class '__main__.PluginB'>]
总结
Python 的魔法函数是语言数据模型的核心,它们允许编写的类无缝集成到 Python 的语法和内置操作中。掌握这些方法不仅可以写出更符合 Python 风格的代码,还能深入理解 Python 内部的工作机制。
使用魔法函数时,应遵循以下原则:
- 在操作不支持的类型时,返回
NotImplemented 而不是抛出异常,以便 Python 尝试反向操作。 - 熟悉文档(Python 数据模型)以了解所有细节。