魔法方法是 Python 对象模型的核心,它们以双下划线开头和结尾(如 init),让你能控制对象的底层行为。掌握魔法方法,就能写出更自然、更 Pythonic 的类,让自定义对象像内置类型一样工作。
构造与析构:init、new、del
init(self, ...)
实例化后最常用到的初始化方法。当调用 MyClass(...) 时,Python 先创建对象,然后自动调用 init 进行初始化。不要在这里返回任何值(除了 None)。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
new(cls, ...)
在 init 之前被调用,负责创建并返回实例对象。通常不需要重写,除非你要控制单例模式、继承不可变类型(如 str、int)时。
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
del(self)
对象被垃圾回收前调用,类似于“析构函数”。但不要依赖它做关键资源释放,因为调用时机不确定;对于文件、连接等资源,请使用上下文管理器 (with)。
class FileWrapper:
def __init__(self, filename):
self.file = open(filename, 'w')
def __del__(self):
self.file.close() # 仅作最终保障,不推荐
运算符重载:让你的对象支持 +、-、== 等
- 算术运算符:
add(+)、sub(-)、mul(*)、truediv(/)等。
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
重载后 v1 + v2 就能直接使用。
- 比较运算符:
eq(==)、lt(<)、gt(>)等。实现eq后,两个不同实例可以按值比较;如果你还想让对象可排序,就补上lt等方法。
- 字符串表示:
repr(self):返回“官方”字符串表示,应尽可能准确、明确,eval(repr(obj))最好能重建对象。str(self):返回“非正式”可读性好的字符串,被print()和str()调用。
如果只定义 repr,str 默认会回退到它。
- 其他常用:
len(len())、getitem(索引访问[])、setitem、contains(in操作)等,可以让你的类表现得像内置容器。
属性访问控制:getattr、setattr、getattribute
getattr(self, name)
仅当通过常规途径找不到属性时被调用。适合实现“惰性属性”或兼容老代码。
class LazyRecord:
def __init__(self):
self._data = {}
def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
setattr(self, name, value)
在每次给属性赋值时被调用,哪怕是 self.xxx = ... 也会触发。重写时要小心死循环:内部必须用 super().setattr() 或直接操作 dict 来真正存储。
class Validated:
def __setattr__(self, name, value):
if name == 'age' and value < 0:
raise ValueError("age cannot be negative")
super().__setattr__(name, value)
getattribute(self, name)
在每次访问属性时无条件被调用,包括访问 dict 本身,极易触发无限递归。除非你很明确在做什么,绝大多数情况用 getattr 即可。
容器模拟:让类变成列表/字典的亲戚
实现以下方法,你的对象就能配合 len()、[]、for 循环等操作:
| 方法 | 触发操作 |
|------|----------|
| len | len(obj) |
| getitem | obj[key],迭代需支持 IndexError 结束 |
| setitem | obj[key] = value |
| delitem | del obj[key] |
| contains | item in obj |
| iter | for x in obj:,返回一个迭代器 |
示例:可循环的页码范围
class RangeWrapper:
def __init__(self, start, end):
self.start = start
self.end = end
def __len__(self):
return max(0, self.end - self.start)
def __getitem__(self, index):
value = self.start + index
if value >= self.end:
raise IndexError
return value
可调用对象:call
实现了 call 方法的实例可以像函数一样被调用。这常用于创建更灵活的函数对象或装饰器。
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(5)) # 10
上下文管理:enter 与 exit
让你可以用 with 语句管理资源。
enter:进入with块时执行,返回值赋给as后的变量。exit(exc_type, exc_val, exc_tb):离开with块时调用,异常信息作为参数传入,返回True可以吞掉异常。
典型用法:自定义文件打开
class ManagedFile:
def __init__(self, name, mode='r'):
self.name = name
self.mode = mode
def __enter__(self):
self.file = open(self.name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
# 不处理异常,返回 False(默认)让异常继续传播
用 with ManagedFile('data.txt', 'w') as f: 即可安全读写文件,即使内部抛出异常文件也会被关闭。
实际开发提示
- 不要为了炫技而滥用魔法方法,只在需要让对象表现得更自然地内置类型时才实现。
- Python 3.7 引入的
dataclasses能自动帮你生成init、repr、eq等,大幅减少样板代码。若只需存储数据,优先考虑dataclasses或namedtuple。 - 当需要使用
setattr、getattribute时,先想清楚是否有更简单的方式(如@property)。