# Python常见魔术方法有哪些?
面试官问:"Python的魔术方法有哪些?它们有什么用?"
很多人只知道__init__,但Python有几十个魔术方法。真正要讲清楚的是:魔术方法(双下划线方法,也叫dunder methods)让对象能支持特定语法和协议,比如运算符、索引、迭代、字符串表示、上下文管理等。 通过实现对应的魔术方法,你的自定义类可以像内置类型一样使用。
# 简要回答
魔术方法是以双下划线开头和结尾的特殊方法,如__init__、__str__、__add__。它们不应该直接调用,而是通过特定语法触发。
常见分类包括:
- 对象创建和初始化:
__new__、__init__、__del__ - 字符串表示:
__str__、__repr__ - 运算符重载:
__add__、__eq__、__lt__等 - 容器协议:
__len__、__getitem__、__setitem__、__iter__ - 属性访问:
__getattr__、__setattr__、__delattr__ - 上下文管理:
__enter__、__exit__ - 可调用对象:
__call__
实现魔术方法能让自定义类支持Python的语法糖,使代码更简洁、更符合Python风格。
# 详细回答
# 对象创建和生命周期
__new__(cls, ...)
创建对象,返回实例。通常不需要重写,除非要控制对象创建(单例、不可变类型)。
__init__(self, ...)
初始化对象,设置属性。最常用的魔术方法。
__del__(self)
对象被销毁时调用,但不可靠。Python的垃圾回收时机不确定,不要在__del__里做关键清理工作(用上下文管理器代替)。
class Resource:
def __init__(self, name):
self.name = name
print(f"Resource {name} created")
def __del__(self):
print(f"Resource {self.name} deleted")
r = Resource("file")
del r # Resource file deleted(可能延迟)
2
3
4
5
6
7
8
9
10
# 字符串表示
__str__(self)
str(obj)或print(obj)时调用,返回给人看的字符串。
__repr__(self)
repr(obj)或交互式环境显示对象时调用,返回给开发者看的字符串,理想情况下应该是能重建对象的表达式。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point at ({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p) # Point at (3, 4),调用__str__
print(repr(p)) # Point(3, 4),调用__repr__
2
3
4
5
6
7
8
9
10
11
12
13
14
最佳实践:至少实现__repr__。如果没有__str__,str(obj)会回退到__repr__。
# 运算符重载
比较运算符:
__eq__(self, other):==__ne__(self, other):!=__lt__(self, other):<__le__(self, other):<=__gt__(self, other):>__ge__(self, other):>=
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True
print(p1 < Point(3, 4)) # True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
算术运算符:
__add__(self, other):+__sub__(self, other):-__mul__(self, other):*__truediv__(self, other):/__floordiv__(self, other)://__mod__(self, other):%
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
2
3
4
5
6
7
8
9
10
11
12
13
14
反向运算符(当左操作数不支持时):
__radd__、__rsub__等
就地运算符:
__iadd__、__isub__等,对应+=、-=
# 容器协议
__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时调用。
class MyList:
def __init__(self):
self._items = []
def __len__(self):
return len(self._items)
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, value):
self._items[index] = value
def __contains__(self, item):
return item in self._items
def append(self, item):
self._items.append(item)
ml = MyList()
ml.append(10)
ml.append(20)
print(len(ml)) # 2
print(ml[0]) # 10
ml[0] = 15
print(10 in ml) # False
print(15 in ml) # True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__iter__(self)
返回迭代器,支持for item in obj。
__next__(self)
迭代器的next()方法。
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
for i in CountDown(3):
print(i) # 3, 2, 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 属性访问
__getattr__(self, name)
访问不存在的属性时调用。
__setattr__(self, name, value)
设置属性时调用。
__delattr__(self, name)
删除属性时调用。
__getattribute__(self, name)
访问任何属性时都调用(包括存在的),优先级高于__getattr__。
class DynamicAttrs:
def __init__(self):
self.data = {}
def __getattr__(self, name):
if name in self.data:
return self.data[name]
raise AttributeError(f"No attribute '{name}'")
def __setattr__(self, name, value):
if name == 'data':
super().__setattr__(name, value)
else:
self.data[name] = value
obj = DynamicAttrs()
obj.x = 10
print(obj.x) # 10
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 上下文管理
__enter__(self)
with语句进入时调用,返回值赋给as后的变量。
__exit__(self, exc_type, exc_val, exc_tb)
with语句退出时调用,处理异常和清理。
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
return False # 不抑制异常
with FileManager('test.txt', 'w') as f:
f.write('Hello')
# 退出时自动关闭文件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 可调用对象
__call__(self, ...)
让对象可以像函数一样调用。
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(5)) # 10
print(double(10)) # 20
2
3
4
5
6
7
8
9
10
# 其他常见魔术方法
__hash__(self)
返回对象的哈希值,支持hash(obj)和作为字典键。
__bool__(self)
bool(obj)或if obj时调用,返回布尔值。
__format__(self, format_spec)
format(obj, spec)或f-string的格式化。

# 知识拓展
Q1:魔术方法可以直接调用吗?
技术上可以,但不推荐。魔术方法是给解释器调用的:
# 不推荐
obj.__str__()
# 推荐
str(obj)
2
3
4
5
直接调用魔术方法会绕过一些检查和优化。
Q2:为什么叫魔术方法?
因为它们通过特殊语法"魔法般地"被调用。比如obj1 + obj2会自动调用obj1.__add__(obj2)。
Q3:如果不实现__eq__,==会怎样?
默认比较对象身份(地址),等价于is。两个属性相同的对象,用==比较会返回False。
Q4:__getattr__和__getattribute__有什么区别?
__getattribute__:访问任何属性都调用,包括存在的属性。__getattr__:只有属性不存在时才调用。
重写__getattribute__要小心,容易进入无限递归:
class Test:
def __getattribute__(self, name):
# 错误:会递归
# return self.name
# 正确:调用父类的__getattribute__
return super().__getattribute__(name)
2
3
4
5
6
7
Q5:有多少个魔术方法?
几十个。常用的包括本文列出的这些,完整列表在Python官方文档 (opens new window)。不需要全部记住,根据需求查文档即可。
上一篇:Python多继承的MRO是什么?super()如何工作?
到此,面向对象这一章的7篇文章全部完成。这些文章覆盖了类与对象、属性、方法、对象创建、封装继承多态、MRO和魔术方法,形成完整的Python面向对象知识体系。
评论
验证登录状态...