Python教程34:魔术方法(Magic Methods)
“命名即魔法。”
Python的魔术方法(也叫特殊方法、双下划线方法)是Python面向对象的核心特性。它们让你的类能够像内置类型一样工作,实现运算符重载、容器协议等高级功能。
1. 什么是魔术方法
魔术方法(Magic Methods):
- 双下划线开头和结尾的方法:
__method__ - Python自动调用,不需要显式调用
- 让类支持Python的特殊语法
- 也叫"dunder methods"(double underscore)
为什么需要魔术方法:
- 运算符重载:让对象支持+、-、*等运算
- 容器协议:让对象可迭代、可索引
- 上下文管理:支持with语句
- 对象表示:自定义打印输出
- 属性访问:控制属性读写
简单示例
1class Point:
2 """二维点"""
3 def __init__(self, x, y):
4 """初始化(魔术方法)"""
5 self.x = x
6 self.y = y
7
8 def __str__(self):
9 """字符串表示(魔术方法)"""
10 return f"Point({self.x}, {self.y})"
11
12 def __add__(self, other):
13 """加法运算符(魔术方法)"""
14 return Point(self.x + other.x, self.y + other.y)
15
16# 使用
17p1 = Point(1, 2)
18p2 = Point(3, 4)
19
20print(p1) # 调用__str__: Point(1, 2)
21p3 = p1 + p2 # 调用__add__
22print(p3) # Point(4, 6)
2. 对象创建和销毁
new:创建实例
1class Singleton:
2 """
3 单例模式
4 - __new__在__init__之前调用
5 - __new__负责创建实例
6 - __new__是类方法
7 """
8 _instance = None
9
10 def __new__(cls):
11 """
12 创建实例
13 - cls是类本身
14 - 必须返回实例
15 """
16 if cls._instance is None:
17 print("Creating singleton instance")
18 cls._instance = super().__new__(cls)
19 return cls._instance
20
21 def __init__(self):
22 """初始化实例"""
23 print("Initializing instance")
24
25# 使用
26s1 = Singleton() # Creating singleton instance, Initializing instance
27s2 = Singleton() # Initializing instance (不创建新实例)
28print(s1 is s2) # True(同一个实例)
init:初始化
我们已经很熟悉了:
1class Person:
2 def __init__(self, name, age):
3 """
4 初始化
5 - 实例已创建,设置初始状态
6 - 没有返回值
7 """
8 self.name = name
9 self.age = age
del:析构
1class Resource:
2 def __init__(self, name):
3 self.name = name
4 print(f"Acquiring resource: {self.name}")
5
6 def __del__(self):
7 """
8 析构方法
9 - 对象被垃圾回收前调用
10 - 用于清理资源
11 - 不要依赖执行时机
12 """
13 print(f"Releasing resource: {self.name}")
14
15# 使用
16r = Resource("Database")
17del r # Releasing resource: Database
3. 对象表示
str:用户友好的字符串
1class Book:
2 def __init__(self, title, author):
3 self.title = title
4 self.author = author
5
6 def __str__(self):
7 """
8 用户友好的字符串表示
9 - print()调用此方法
10 - str()调用此方法
11 - 给最终用户看的
12 """
13 return f"《{self.title}》by {self.author}"
14
15book = Book("Python核心编程", "Wesley Chun")
16print(book) # 调用__str__
17print(str(book)) # 《Python核心编程》by Wesley Chun
repr:开发者字符串
1class Book:
2 def __init__(self, title, author):
3 self.title = title
4 self.author = author
5
6 def __repr__(self):
7 """
8 开发者字符串表示
9 - 调试时看到的
10 - 应该是有效的Python表达式
11 - 没有__str__时,print也会用__repr__
12 """
13 return f"Book('{self.title}', '{self.author}')"
14
15 def __str__(self):
16 return f"《{self.title}》"
17
18book = Book("Python", "Author")
19print(book) # 调用__str__: 《Python》
20print(repr(book)) # 调用__repr__: Book('Python', 'Author')
21print([book]) # [Book('Python', 'Author')](列表用repr)
str vs repr:
__str__:用户友好,print()使用__repr__:开发者友好,调试使用- 至少实现
__repr__ - 最好都实现
4. 运算符重载
算术运算符
1class Vector:
2 """二维向量"""
3 def __init__(self, x, y):
4 self.x = x
5 self.y = y
6
7 def __add__(self, other):
8 """加法: v1 + v2"""
9 return Vector(self.x + other.x, self.y + other.y)
10
11 def __sub__(self, other):
12 """减法: v1 - v2"""
13 return Vector(self.x - other.x, self.y - other.y)
14
15 def __mul__(self, scalar):
16 """乘法: v * 3"""
17 return Vector(self.x * scalar, self.y * scalar)
18
19 def __truediv__(self, scalar):
20 """除法: v / 2"""
21 return Vector(self.x / scalar, self.y / scalar)
22
23 def __neg__(self):
24 """取负: -v"""
25 return Vector(-self.x, -self.y)
26
27 def __abs__(self):
28 """绝对值/模: abs(v)"""
29 return (self.x ** 2 + self.y ** 2) ** 0.5
30
31 def __str__(self):
32 return f"Vector({self.x}, {self.y})"
33
34# 使用
35v1 = Vector(1, 2)
36v2 = Vector(3, 4)
37
38print(v1 + v2) # Vector(4, 6)
39print(v1 - v2) # Vector(-2, -2)
40print(v1 * 3) # Vector(3, 6)
41print(v1 / 2) # Vector(0.5, 1.0)
42print(-v1) # Vector(-1, -2)
43print(abs(v1)) # 2.23606...
比较运算符
1class Person:
2 def __init__(self, name, age):
3 self.name = name
4 self.age = age
5
6 def __eq__(self, other):
7 """等于: =="""
8 return self.age == other.age
9
10 def __ne__(self, other):
11 """不等于: !="""
12 return self.age != other.age
13
14 def __lt__(self, other):
15 """小于: <"""
16 return self.age < other.age
17
18 def __le__(self, other):
19 """小于等于: <="""
20 return self.age <= other.age
21
22 def __gt__(self, other):
23 """大于: >"""
24 return self.age > other.age
25
26 def __ge__(self, other):
27 """大于等于: >="""
28 return self.age >= other.age
29
30 def __str__(self):
31 return f"{self.name}({self.age})"
32
33# 使用
34p1 = Person("Alice", 25)
35p2 = Person("Bob", 30)
36
37print(p1 == p2) # False
38print(p1 < p2) # True
39print(p1 <= p2) # True
40
41# 可以排序
42people = [p2, p1, Person("Charlie", 20)]
43sorted_people = sorted(people) # 使用__lt__排序
44for p in sorted_people:
45 print(p)
使用functools.total_ordering简化:
1from functools import total_ordering
2
3@total_ordering
4class Person:
5 def __init__(self, name, age):
6 self.name = name
7 self.age = age
8
9 def __eq__(self, other):
10 """只需实现=="""
11 return self.age == other.age
12
13 def __lt__(self, other):
14 """只需实现<"""
15 return self.age < other.age
16
17 # 其他比较运算符自动生成
5. 容器协议
len__和__getitem
1class Playlist:
2 """播放列表"""
3 def __init__(self):
4 self.songs = []
5
6 def add(self, song):
7 """添加歌曲"""
8 self.songs.append(song)
9
10 def __len__(self):
11 """
12 len(playlist)
13 - 返回容器大小
14 """
15 return len(self.songs)
16
17 def __getitem__(self, index):
18 """
19 playlist[0]
20 - 支持索引访问
21 - 支持切片
22 - 支持迭代
23 """
24 return self.songs[index]
25
26 def __setitem__(self, index, value):
27 """
28 playlist[0] = "new song"
29 - 支持索引赋值
30 """
31 self.songs[index] = value
32
33 def __delitem__(self, index):
34 """
35 del playlist[0]
36 - 支持删除元素
37 """
38 del self.songs[index]
39
40 def __contains__(self, item):
41 """
42 "song" in playlist
43 - 支持 in 运算符
44 """
45 return item in self.songs
46
47# 使用
48playlist = Playlist()
49playlist.add("Song 1")
50playlist.add("Song 2")
51playlist.add("Song 3")
52
53print(len(playlist)) # 3
54print(playlist[0]) # Song 1
55print(playlist[1:3]) # ['Song 2', 'Song 3']
56print("Song 1" in playlist) # True
57
58# 迭代(因为有__getitem__)
59for song in playlist:
60 print(song)
iter__和__next
1class Countdown:
2 """倒计时迭代器"""
3 def __init__(self, start):
4 self.current = start
5
6 def __iter__(self):
7 """
8 返回迭代器
9 - for循环会调用
10 - iter()会调用
11 """
12 return self
13
14 def __next__(self):
15 """
16 返回下一个元素
17 - next()会调用
18 - 没有元素时抛出StopIteration
19 """
20 if self.current <= 0:
21 raise StopIteration
22
23 self.current -= 1
24 return self.current + 1
25
26# 使用
27for num in Countdown(5):
28 print(num) # 5, 4, 3, 2, 1
6. 上下文管理器
enter__和__exit
1class DatabaseConnection:
2 """数据库连接"""
3 def __init__(self, db_name):
4 self.db_name = db_name
5 self.connection = None
6
7 def __enter__(self):
8 """
9 进入with块时调用
10 - 获取资源
11 - 返回值赋给as后的变量
12 """
13 print(f"Connecting to {self.db_name}")
14 self.connection = f"Connection to {self.db_name}"
15 return self.connection
16
17 def __exit__(self, exc_type, exc_value, traceback):
18 """
19 离开with块时调用
20 - 释放资源
21 - exc_*:异常信息(无异常时为None)
22 - 返回True抑制异常
23 """
24 print(f"Closing connection to {self.db_name}")
25 self.connection = None
26 return False # 不抑制异常
27
28# 使用
29with DatabaseConnection("mydb") as conn:
30 print(f"Working with {conn}")
31# 自动调用__exit__
7. 属性访问
getattr、setattr、delattr
1class DynamicObject:
2 """动态属性对象"""
3 def __init__(self):
4 # 必须用object.__setattr__避免递归
5 object.__setattr__(self, '_data', {})
6
7 def __getattr__(self, name):
8 """
9 访问不存在的属性时调用
10 - obj.name
11 - 不存在的属性才调用
12 """
13 if name in self._data:
14 return self._data[name]
15 raise AttributeError(f"No attribute '{name}'")
16
17 def __setattr__(self, name, value):
18 """
19 设置属性时调用
20 - obj.name = value
21 - 所有属性赋值都会调用
22 """
23 if name.startswith('_'):
24 object.__setattr__(self, name, value)
25 else:
26 self._data[name] = value
27
28 def __delattr__(self, name):
29 """
30 删除属性时调用
31 - del obj.name
32 """
33 if name in self._data:
34 del self._data[name]
35 else:
36 raise AttributeError(f"No attribute '{name}'")
37
38# 使用
39obj = DynamicObject()
40obj.x = 10 # 调用__setattr__
41print(obj.x) # 调用__getattr__: 10
42del obj.x # 调用__delattr__
8. 可调用对象
call
1class Multiplier:
2 """乘法器"""
3 def __init__(self, factor):
4 self.factor = factor
5
6 def __call__(self, x):
7 """
8 让对象可调用
9 - obj(x) 像函数一样
10 """
11 return x * self.factor
12
13# 使用
14times_3 = Multiplier(3)
15print(times_3(10)) # 30(像函数一样调用)
16print(times_3(5)) # 15
17
18# 检查是否可调用
19print(callable(times_3)) # True
9. 实战示例:自定义列表
1class MyList:
2 """自定义列表类"""
3 def __init__(self, *items):
4 self._items = list(items)
5
6 def __len__(self):
7 return len(self._items)
8
9 def __getitem__(self, index):
10 return self._items[index]
11
12 def __setitem__(self, index, value):
13 self._items[index] = value
14
15 def __delitem__(self, index):
16 del self._items[index]
17
18 def __iter__(self):
19 return iter(self._items)
20
21 def __contains__(self, item):
22 return item in self._items
23
24 def __add__(self, other):
25 """支持+运算"""
26 return MyList(*(self._items + other._items))
27
28 def __mul__(self, n):
29 """支持*运算"""
30 return MyList(*(self._items * n))
31
32 def __str__(self):
33 return f"MyList({self._items})"
34
35 def __repr__(self):
36 return f"MyList{tuple(self._items)}"
37
38 def append(self, item):
39 """添加元素"""
40 self._items.append(item)
41
42# 使用
43ml = MyList(1, 2, 3)
44print(len(ml)) # 3
45print(ml[0]) # 1
46print(2 in ml) # True
47print(ml + MyList(4, 5)) # MyList([1, 2, 3, 4, 5])
48print(ml * 2) # MyList([1, 2, 3, 1, 2, 3])
49
50for item in ml:
51 print(item)
10. 常用魔术方法速查
| 类别 | 方法 | 说明 |
|---|---|---|
| 创建销毁 | __new__ | 创建实例 |
__init__ | 初始化实例 | |
__del__ | 析构 | |
| 表示 | __str__ | str()和print() |
__repr__ | repr()和调试 | |
__format__ | format() | |
| 算术 | __add__ | + |
__sub__ | - | |
__mul__ | * | |
__truediv__ | / | |
| 比较 | __eq__ | == |
__ne__ | != | |
__lt__ | < | |
__gt__ | > | |
| 容器 | __len__ | len() |
__getitem__ | obj[key] | |
__setitem__ | obj[key] = value | |
__contains__ | in | |
| 迭代 | __iter__ | iter() |
__next__ | next() | |
| 上下文 | __enter__ | with进入 |
__exit__ | with退出 | |
| 属性 | __getattr__ | 访问不存在的属性 |
__setattr__ | 设置属性 | |
| 调用 | __call__ | () |
11. 小结
今天我们学习了魔术方法:
- 定义:
__method__双下划线方法 - 对象生命周期:
__new__、__init__、__del__ - 对象表示:
__str__、__repr__ - 运算符重载:算术、比较
- 容器协议:
__len__、__getitem__、__iter__ - 上下文管理:
__enter__、__exit__ - 属性访问:
__getattr__、__setattr__ - 可调用对象:
__call__
魔术方法让Python对象更强大、更Pythonic,是高级OOP的关键。
练习题:
- 创建一个Matrix类,实现矩阵的加法、乘法运算
- 实现一个Range类,支持迭代和索引
- 创建一个配置对象,支持点号访问(obj.key)和字典访问(obj[‘key’])
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
