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. 属性访问

getattrsetattrdelattr

 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的关键。


练习题

  1. 创建一个Matrix类,实现矩阵的加法、乘法运算
  2. 实现一个Range类,支持迭代和索引
  3. 创建一个配置对象,支持点号访问(obj.key)和字典访问(obj[‘key’])

本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读