魔术方法是什么?(定义)
魔术方法(Magic Methods)是 Python 类中前后各有两个下划线的特殊方法(也叫 “双下划线方法”“Dunder Methods”),核心特点是:
- 由 Python 自动调用,无需手动触发;
- 用于实现类的特殊行为(如实例创建、字符串表示、比较、算术运算等),让类的行为更像内置类型。
分类详解与代码示例
1. 构造与初始化(实例生命周期)
核心方法
| 方法 |
作用 |
调用时机 |
__new__(cls, ...) |
创建实例(分配内存空间),是类的 “静态方法”(第一个参数是 cls) |
在 __init__ 之前执行 |
__init__(self, ...) |
初始化实例属性(设置实例的初始状态) |
__new__ 返回实例后自动执行 |
__del__(self) |
析构方法(实例被垃圾回收时调用) |
实例被销毁时自动执行 |
代码示例
class Person:
# __new__:创建实例(通常不需要重写,除非需要控制实例创建过程)
def __new__(cls, name, age):
print(f"__new__ 被调用:正在创建 {cls.__name__} 实例")
# 调用父类(object)的 __new__ 方法创建实例
return super().__new__(cls)
# __init__:初始化实例属性(最常用)
def __init__(self, name, age):
print(f"__init__ 被调用:正在初始化 {name} 的属性")
self.name = name # 实例属性
self.age = age
# __del__:析构方法(不推荐手动使用,依赖垃圾回收时机)
def __del__(self):
print(f"__del__ 被调用:{self.name} 实例被销毁")
# 创建实例(自动调用 __new__ → __init__)
p = Person("Tom", 18)
# 输出:
# __new__ 被调用:正在创建 Person 实例
# __init__ 被调用:正在初始化 Tom 的属性
# 手动删除实例(触发 __del__,但实际开发中不依赖此方法)
del p
# 输出:__del__ 被调用:Tom 实例被销毁
面试重点:__new__ vs __init__
__new__ 负责 “创建实例”(返回一个新对象);
__init__ 负责 “初始化实例”(对 __new__ 返回的对象设置属性,无返回值)。
2. 字符串表示(让类的实例 “可读”)
核心方法
| 方法 |
作用 |
触发场景 |
__str__(self) |
用户友好的字符串表示(给人看的) |
str(obj)、print(obj) |
__repr__(self) |
开发者友好的字符串表示(给程序看的,目标是能重建实例) |
repr(obj)、交互式解释器 |
代码示例
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# __str__:给用户看的简洁描述
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
# __repr__:给开发者看的详细描述(通常包含重建实例的代码)
def __repr__(self):
return f"Person('{self.name}', {self.age})"
p = Person("Tom", 18)
print(str(p)) # 输出:Person(name=Tom, age=18)(调用 __str__)
print(repr(p)) # 输出:Person('Tom', 18)(调用 __repr__)
print(p) # 输出:Person(name=Tom, age=18)(print 默认调用 __str__)
面试重点
- 如果只定义了
__repr__,__str__ 会默认使用 __repr__ 的返回值;
- 建议至少定义
__repr__,保证实例在任何场景下都有清晰的表示。
3. 比较操作(让实例支持 ==、<、> 等)
核心方法
| 方法 |
对应运算符 |
作用 |
__eq__(self, other) |
== |
判断 “等于” |
__ne__(self, other) |
!= |
判断 “不等于”(默认继承 __eq__ 的反逻辑) |
__lt__(self, other) |
< |
判断 “小于” |
__gt__(self, other) |
> |
判断 “大于” |
__le__(self, other) |
<= |
判断 “小于等于” |
__ge__(self, other) |
>= |
判断 “大于等于” |
代码示例
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# __eq__:判断两个实例是否“相等”(按 name 和 age 比较)
def __eq__(self, other):
if not isinstance(other, Person):
return False # 不是 Person 类型,直接返回 False
return self.name == other.name and self.age == other.age
# __lt__:判断“小于”(按 age 比较)
def __lt__(self, other):
if not isinstance(other, Person):
raise TypeError("只能与 Person 类型比较")
return self.age < other.age
p1 = Person("Tom", 18)
p2 = Person("Tom", 18)
p3 = Person("Jerry", 20)
print(p1 == p2) # 输出:True(调用 __eq__)
print(p1 < p3) # 输出:True(调用 __lt__)
print(p1 > p3) # 输出:False(Python 会自动用 __lt__ 的反逻辑推导)
4. 算术运算(让实例支持 +、-、* 等)
| 方法 |
对应运算符 |
作用 |
__add__(self, other) |
+ |
加法 |
__sub__(self, other) |
- |
减法 |
__mul__(self, other) |
* |
乘法 |
__truediv__(self, other) |
/ |
真除法(Python3) |
__floordiv__(self, other) |
// |
地板除法 |
__mod__(self, other) |
% |
取模 |
__pow__(self, other) |
** |
幂运算 |
反向运算(当左侧对象不支持该运算时调用)
| 方法 |
对应场景 |
__radd__(self, other) |
other + self(左侧不是当前类实例) |
__rsub__(self, other) |
other - self |
代码示例
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# __add__:向量加法(+)
def __add__(self, other):
if not isinstance(other, Vector):
raise TypeError("只能与 Vector 相加")
return Vector(self.x + other.x, self.y + other.y)
# __mul__:向量数乘(*)
def __mul__(self, scalar):
if not isinstance(scalar, (int, float)):
raise TypeError("只能与数字相乘")
return Vector(self.x * scalar, self.y * scalar)
# __rmul__:反向数乘(当左侧是数字时调用,如 2 * v)
def __rmul__(self, scalar):
return self.__mul__(scalar) # 复用 __mul__ 的逻辑
# __str__:方便打印
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # 输出:Vector(4, 6)(调用 __add__)
print(v1 * 3) # 输出:Vector(3, 6)(调用 __mul__)
print(3 * v1) # 输出:Vector(3, 6)(调用 __rmul__,因为 3 是 int,没有 __mul__ 方法处理 Vector)
5. 容器操作(让类像列表 / 字典一样工作)
核心方法
| 方法 |
对应操作 |
作用 |
__len__(self) |
len(obj) |
获取长度 |
__getitem__(self, key) |
obj[key] |
获取元素 |
__setitem__(self, key, value) |
obj[key] = value |
设置元素 |
__delitem__(self, key) |
del obj[key] |
删除元素 |
__contains__(self, item) |
item in obj |
成员判断 |
__iter__(self) |
for item in obj |
迭代(返回迭代器) |
代码示例:自定义列表
class MyList:
def __init__(self, data=None):
self.data = data if data is not None else []
# __len__:支持 len()
def __len__(self):
return len(self.data)
# __getitem__:支持 obj[key] 获取元素
def __getitem__(self, index):
return self.data[index]
# __setitem__:支持 obj[key] = value 设置元素
def __setitem__(self, index, value):
self.data[index] = value
# __contains__:支持 item in obj
def __contains__(self, item):
return item in self.data
# __iter__:支持 for 循环迭代
def __iter__(self):
return iter(self.data) # 直接返回 data 的迭代器
ml = MyList([1, 2, 3, 4])
print(len(ml)) # 输出:4(调用 __len__)
print(ml[0]) # 输出:1(调用 __getitem__)
ml[0] = 100 # 调用 __setitem__
print(ml[0]) # 输出:100
print(2 in ml) # 输出:True(调用 __contains__)
for item in ml: # 调用 __iter__
print(item, end=" ") # 输出:100 2 3 4
6. 上下文管理(支持 with 语句,自动管理资源)
核心方法
| 方法 |
作用 |
__enter__(self) |
进入 with 块前执行,返回值会赋值给 as 后的变量(可选)。 |
__exit__(self, exc_type, exc_val, exc_tb) |
离开 with 块时执行(无论是否发生异常),用于资源清理。参数:- exc_type:异常类型(无异常时为 None)- exc_val:异常值- exc_tb:异常追踪信息 |
代码示例:自定义文件管理器
class MyFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
# __enter__:进入 with 块时执行,返回文件对象
def __enter__(self):
print(f"打开文件:{self.filename}")
self.file = open(self.filename, self.mode)
return self.file # 返回值给 as 后的变量
# __exit__:离开 with 块时执行,关闭文件
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"关闭文件:{self.filename}")
self.file.close()
# 如果返回 True,表示异常已处理,不向外抛出;返回 None/False 则继续抛出
if exc_type:
print(f"捕获异常:{exc_val}")
return True
# 使用 with 语句(自动调用 __enter__ 和 __exit__)
with MyFile("test.txt", "w") as f:
f.write("Hello, Magic Methods!")
# 故意触发异常,测试 __exit__ 的异常处理
# raise ValueError("测试异常")
7. 属性访问(控制实例属性的获取、设置、删除)
核心方法
| 方法 |
作用 |
触发场景 |
__getattr__(self, name) |
访问不存在的属性时调用 |
obj.nonexistent_attr |
__getattribute__(self, name) |
访问任何属性时调用(优先级最高,需谨慎使用,避免无限递归) |
访问任何属性 |
__setattr__(self, name, value) |
设置属性时调用 |
obj.attr = value |
__delattr__(self, name) |
删除属性时调用 |
del obj.attr |
代码示例
class Person:
def __init__(self, name):
self.name = name # 会调用 __setattr__
# __getattr__:只在访问“不存在”的属性时调用
def __getattr__(self, name):
return f"⚠️ 属性 '{name}' 不存在"
# __setattr__:设置任何属性时调用
def __setattr__(self, name, value):
print(f"设置属性:{name} = {value}")
# 注意:不能直接用 self.name = value,会无限递归!
# 必须用 super().__setattr__ 或 self.__dict__[name] = value
super().__setattr__(name, value)
# __delattr__:删除属性时调用
def __delattr__(self, name):
print(f"删除属性:{name}")
super().__delattr__(name)
p = Person("Tom")
# 输出:设置属性:name = Tom
print(p.name) # 输出:Tom(存在的属性,不调用 __getattr__)
print(p.age) # 输出:⚠️ 属性 'age' 不存在(不存在的属性,调用 __getattr__)
p.age = 18 # 输出:设置属性:age = 18(调用 __setattr__)
del p.age # 输出:删除属性:age(调用 __delattr__)
面试重点
__getattr__ 只在属性不存在时调用;
__getattribute__ 会拦截所有属性访问,使用时必须通过 super().__getattribute__(name) 获取属性,否则会无限递归。
8. 可调用对象(让实例像函数一样被调用)
核心方法
| 方法 |
作用 |
触发场景 |
__call__(self, ...) |
让实例可以像函数一样被调用 |
obj(...) |
代码示例:计数器
class Counter:
def __init__(self):
self.count = 0
# __call__:让实例可调用,每次调用 count +1
def __call__(self, *args, **kwargs):
self.count += 1
print(f"已调用 {self.count} 次")
return self.count
c = Counter()
c() # 输出:已调用 1 次(调用 __call__)
c() # 输出:已调用 2 次
c() # 输出:已调用 3 次
9. 类型转换(让实例支持 int()、float()、bool() 等)
核心方法
| 方法 |
对应操作 |
作用 |
__int__(self) |
int(obj) |
转换为整数 |
__float__(self) |
float(obj) |
转换为浮点数 |
__bool__(self) |
bool(obj) |
转换为布尔值(用于 if 判断) |
__str__(self) |
str(obj) |
转换为字符串(前面已讲) |
代码示例
class MyNumber:
def __init__(self, value):
self.value = value
def __int__(self):
return int(self.value)
def __float__(self):
return float(self.value)
def __bool__(self):
return self.value > 0 # 大于 0 为 True,否则为 False
n = MyNumber(3.14)
print(int(n)) # 输出:3(调用 __int__)
print(float(n)) # 输出:3.14(调用 __float__)
print(bool(n)) # 输出:True(调用 __bool__)
if n: # 自动调用 __bool__
print("n 是正数")
总结
魔术方法是 Python 面向对象编程的 “灵魂”,通过重写它们,你可以让类的行为完全符合你的预期。面试中重点掌握:
- 构造初始化:
__new__、__init__;
- 字符串表示:
__str__、__repr__;
- 上下文管理:
__enter__、__exit__;
- 容器操作:
__len__、__getitem__;
- 可调用对象:
__call__。
所有评论(0)