魔法方法
魔法方法,也就是特殊方法,是以双下划线开头和结尾的方法,比如__init__、__str__这些。它们被Python解释器自动调用,用来实现对象的特定行为。python 中有很多这样的魔法方法。为了了解清楚各个魔法方法的用处,特此做了一个笔记。
通常魔法方法可以分为几个大类:构造与初始化、字符串表示、属性访问、容器类、比较操作、算术运算、迭代器、上下文管理等等。
说明
首先,构造与初始化最常用的是__init__,还有__new__。用户可能知道__init__是初始化方法,但__new__是实际创建实例的方法。如果用户没有重写以上魔法方法,在创建一个对象时,会先执行__new__ 创建对象 然后执行__init__进行初始化对象。这样用户就可以获取到一个完整的对象
然后是字符串表示,比如__str__和__repr__,这两个方法经常被混淆。需要说明它们的不同用途,__str__用于用户友好的字符串表示,而__repr__用于开发者调试,通常可以用eval()重新生成对象。
接下来是属性访问,比如__getattr__ |__setattr__ |__delattr__,这些方法用于管理属性的访问、设置和删除。还有__getattribute__,但要注意使用时的递归问题。
容器类的方法包括__len__ |__getitem__|__setitem__|__delitem__,这些让对象表现得像列表或字典。迭代相关的有__iter__和__next__,用于支持循环操作。
比较操作的方法,比如__eq__|__lt__等,用来重载比较运算符。算术运算的方法如__add__|__sub__,允许对象进行数学运算。还有反向运算和增量赋值的方法,比如__radd__|__iadd__。
上下文管理是通过__enter__和__exit__实现的,用于with语句,处理资源的获取和释放。
此外,还有__call__方法,让实例可以像函数一样被调用,__getstate__和__setstate__用于序列化,__slots__用来优化内存使用等。
总结与示例
对象的构造与销毁
方法 | 触发场景 | 典型应用 |
---|---|---|
_new_(cls, …) | 实例创建时 | .单例模式、不可变类 |
_init_(self, …) | 实例初始化时 | .属性赋值、资源初始化 |
_del_(self) | 对象销毁时 | .资源清理(不推荐依赖 |
通过代码演示:
class Student(object):def __new__(cls, *args, **kwargs):print("__new__ 方法执行了。。。。")return super().__new__(cls) # 注意 __new__ 方法是返回创建好的空对象,一定要返回,不然__init__方法不会被调用def __init__(self, name, score):print("__init__ 方法执行了。。。。")self.name = nameself.score = scoreif __name__ == "__main__":student = Student("张三", 100)
输出:
__new__ 方法执行了。。。。
__init__ 方法执行了。。。。进程已结束,退出代码为 0
可以看到,当我们创建对象的时候,是先执行的__new__ 然后在执行__init__ 进行对象初始化
字符串类型
方法 | 触发场景 | 典型应用 |
---|---|---|
_str_(self) | print(obj)/str(obj) | 用户友好展示 |
_repr_(self) | 交互式环境/repr(obj) | 明确对象信息,可用eval解析 |
代码展示:
from datetime import datetoday = date(2025, 3, 3)print(str(today)) # 输出: 2023-10-05 (用户友好)
print(repr(today)) # 输出: datetime.date(2023, 10, 5) (可eval重建对象)class Product:def __init__(self, name, price):self.name = nameself.price = pricedef __str__(self):print("__str__ 方法执行了。。。。")return f"{self.name} ¥{self.price}"def __repr__(self):print("__repr__ 方法执行了。。。。")return f"Product(name='{self.name}', price={self.price})"p = Product("Python书籍", 99.9)
print(p) # Python书籍 ¥99.9
print(repr(p)) # Product(name='Python书籍', price=99.9)
设计原则
- _str_:假设读者是最终用户,展示关键信息
- _repr_:假设读者是开发者,包含足够调试信息
- 黄金规则:eval(repr(obj)) 应能重建对象(理想情况)
对象属性类
方法 | 触发场景 | 典型应用 |
---|---|---|
_getattr_(self, name) | 访问不存在的属性时 | 处理缺失属性逻辑 |
_getattribute_(self, name) | 每次属性访问时 | 控制所有属性访问 |
_setattr_(self, name, value) | 设置属性时 | 拦截属性赋值操作 |
_delattr_(self, name) | 删除属性时 | 拦截属性删除操作 |
class Attr:def __getattr__(self, item):print("__getattr__ 方法执行了。。。。")raise AttributeError(f"{item} 不存在")def __setattr__(self, key, value):print("__setattr__ 方法执行了。。。。")super().__setattr__(key, value)def __delattr__(self, item):print("__delattr__ 方法执行了。。。。")del self.__dict__[item]def __getattribute__(self, item):print("__getattribute__ 方法执行了。。。。")return super().__getattribute__(item)att = Attr()
att.existing = "test"
print("=" * 10)
print(att.existing)
# 流程:
# 1. __getattribute__('existing') → 找到属性值
# 2. __getattr__ 不执行
print("=" * 10)
print(att.non_existing)
# 流程:
# 1. __getattribute__('non_existing') → 抛出AttributeError
# 2. 解释器捕获异常后调用__getattr__('non_existing')del att.existing
# 流程:
# 1. __delattr__('existing') -> __getattribute__获取元素。
# 2. 删除元素。
容器类型
方法 | 触发场景 | 典型应用 |
---|---|---|
_len_(self) | len(obj) | 返回整数 |
_getitem_(self, key) | obj[key] | 获取元素,支持索引/切片 |
_setitem_(self, key, value) | obj[key] = value | 修改元 |
_contains_(self, item) | item in obj | 成员判断 |
代码展示:
class CustomList:# 初始化对象def __init__(self, data):self.data = list(data)def __len__(self):print("__len__ 方法执行了。。。。")return len(self.data) # 获取长度def __getitem__(self, index):print("__getitem__ 方法执行了。。。。")return self.data[index] # 获取索引对应的值def __setitem__(self, index, value):print("__setitem__ 方法执行了。。。。")self.data[index] = value # 设置索引对应的值def __contains__(self, item):print("__contains__ 方法执行了。。。。")return item in self.data # 判断是否包含某元素cl = CustomList([1, 2, 3])
print(len(cl)) # __len__ 方法执行了。。。。 3
print(cl[1]) # __getitem__ 方法执行了。。。。 2
print(3 in cl) # __contains__ 方法执行了。。。。 True
运算符重载
方法 | 运算符 | 反向方法 |
---|---|---|
_add_(self, other) | + | _radd_(self, other) |
_sub_(self, other) | - | _rsub_(self, other) |
_mul_(self, other) | * | _rmul_(self, other) |
_eq_(self, other) | == | 无 |
代码展示:
class Meter:def __init__(self, value):self.value = valuedef __add__(self, other):print("触发 __add__")if isinstance(other, Meter):return Meter(self.value + other.value)elif isinstance(other, (int, float)):return Meter(self.value + other)return NotImplementeddef __radd__(self, other):print("触发 __radd__")return self + other # 委托给 __add__# 测试案例
m = Meter(3)
print((m + 2).value) # 正常调用 __add__ → Meter(5)
print((2 + m).value) # 触发流程:# 1. int(2) 无 __add__ 处理 Meter# 2. 调用 m.__radd__(2) → Meter(5)
# 输出:
# 触发 __add__
# 5
# 触发 __radd__
# 触发 __add__
# 5
举例说明 如果 a + b 如果a 中_add_ 方法能处理这个运算,则执行。如果处理不了,则会调用b 的__radd__方法去处理。
设计原则
- 对称性:实现正向方法时应考虑反向方法
- 类型检查:在反向方法中验证操作数类型
- 性能优化:避免在反向方法中重复正向逻辑
上下文管理
方法 | 触发场景 | 典型应用 |
---|---|---|
_enter_(self) | with语句开始时 | 返回需要管理的资源 |
_exit_(self, exc_type, exc_val, traceback) | with语句结束时 | 处理异常,释放资源 |
代码演示:
class SafeFile:def __init__(self, filename):self.filename = filenamedef __enter__(self):print("__enter__ 方法执行了。。。。")self.file = open(self.filename, 'r')return self.filedef __exit__(self, exc_type, exc_val, traceback):print("__exit__ 方法执行了。。。。")self.file.close() # 关闭文件if exc_type is not None: # 如果有异常,则打印异常信息print(f"Error occurred: {exc_val}")return True # 抑制异常with SafeFile('../test.txt') as f:content = f.read()# 控制台输出:
# __enter__ 方法执行了。。。。
# __exit__ 方法执行了。。。。
高级魔法方法应用
描述符协议
方法 | 作用 | 应用场景 |
---|---|---|
_get_(self, instance, owner) | 获取属性值 | 数据验证、延迟计算 |
_set_(self, instance, value) | 设置属性值 | 类型检查、触发更新 |
代码演示:
class Typed:def __init__(self, type_):self.type = type_ # 存储期望的数据类型def __set_name__(self, owner, name):print(f"__set_name__ 方法执行了。。。。{self},{owner},{name}")# 自动获取属性名称(Python 3.6+)self.name = namedef __get__(self, instance, owner):print("__get__ 方法执行了。。。。")# 直接从实例字典中取值return instance.__dict__.get(self.name)def __set__(self, instance, value):print("__set__ 方法执行了。。。。")# 类型检查核心逻辑if not isinstance(value, self.type):raise TypeError(f"{self.name} 必须是 {self.type}")instance.__dict__[self.name] = valueclass Person:name = Typed(str) # 类属性绑定描述符age = Typed(int) # 每个描述符实例独立工作# 使用示例
p = Person()
p.name = "wyz" # ✅ 合法
p.age = 18 # ✅ 合法try:p.name = 123 # ❌ 触发 TypeError: name 必须是 <class 'str'>
except TypeError as e:print(e)try:p.age = "25" # ❌ 触发 TypeError: age 必须是 <class 'int'>
except TypeError as e:print(e)
# 输出:
# __set_name__ 方法执行了。。。。<__main__.Typed object at 0x11d4b6b50>,<class '__main__.Person'>,name
# __set_name__ 方法执行了。。。。<__main__.Typed object at 0x11d4b6af0>,<class '__main__.Person'>,age
# __set__ 方法执行了。。。。
# __set__ 方法执行了。。。。
# __set__ 方法执行了。。。。
# name 必须是 <class 'str'>
# __set__ 方法执行了。。。。
# age 必须是 <class 'int'>
迭代器协议
方法 | 迭代阶段 | 实现要求 |
---|---|---|
_iter_(self) | 迭代开始 | 返回迭代器对象 |
_next_(self) | 每次迭代取值 | 抛出StopIteration终止 |
代码示例:
class Fibonacci:def __init__(self, limit):self.a = 0self.b = 1self.limit = limitdef __iter__(self):return selfdef __next__(self):if self.a > self.limit:raise StopIterationcurrent = self.aself.a, self.b = self.b, self.a + self.breturn currentfor n in Fibonacci(100):print(n) # 输出0,1,1,2,3,5...89
可调用对象协议(call)
让实例像函数一样被调用。这通常用于创建可调用对象,比如装饰器类。我需要解释__call__的作用。
示例1:实例像函数一样被调用
class Accumulator:def __init__(self):self.total = 0def __call__(self, value):self.total += valuereturn self.totalacc = Accumulator()
print(acc(5)) # 输出: 5
print(acc(10)) # 输出: 15
示例2: 使用__call__ 实现一个装饰器
class LoggerDecorator:def __init__(self, func):self.func = funcdef __call__(self, *args):print(f"调用函数: {self.func.__name__}")return self.func(*args)@LoggerDecorator
def add(a, b):return a + bprint(add(3,5)) # 先输出日志再返回结果
序列化控制__getstate__和__setstate__的使用
用于序列化。当对象被pickle时,默认会保存__dict__,但有时需要控制序列化的数据
方法 | 触发时机 | 主要作用 |
---|---|---|
_getstate_ | 对象被序列化时 | 返回可序列化的状态字典 |
_setstate_ | 对象反序列化时 | 根据状态重建对象 |