Python的魔法方法是Python面向对象编程的精髓,理解魔法方法不仅能让开发者写出更优雅的代码,更重要的是能让你深入理解Python对象模型的工作原理。那么今天我们就来系统性的梳理一下Python魔法方法的基础知识、常用魔法方法与分类,以及应用场景实践。

什么是魔法方法?

想象一下这样的场景:当你写下 len([1, 2, 3]) 时,Python是如何知道要返回3的?当你使用 + 连接两个字符串时,背后发生了什么?答案就藏在魔法方法中。

1
2
3
4
# 这些看似普通的操作,背后都有魔法方法在工作
print(len([1, 2, 3])) # 调用了 list.__len__()
print("hello" + "world") # 调用了 str.__add__()
print(str(42)) # 调用了 int.__str__()

魔法方法是Python中以双下划线开头和结尾的特殊方法,也称为双下划线方法(如__init____str____new__等)。它们是Python中的一种协议机制,也是面向对象编程的核心机制之一,赋予了开发者自定义类行为的能力,让自定义类能像内置类型一样与Python语法无缝集成。

了解并理解魔法方法

对于我们Pythoner来说,不要把它们看作简单的”特殊方法”,而应该理解为:

  • 是协议的实现:魔法方法是Python鸭子类型的核心体现
  • 是语言的扩展点:通过它们可以扩展Python语言本身的能力
  • 是语法的委托机制:Python的语法很大程度上委托给对象本身来定义
    正如Python之禅所说:”Python应该提供一种方法,但最好是只有一种明显的方法来做这件事。”魔法方法正是这种哲学的完美体现。

魔法方法的特点与分类

魔法方法是由Python解释器在特定场景下自动调用,无需显示调用,例如当你使用len(obj) 时,Python会调用对象的__len__方法。
由于魔法方法种类繁多,根据功能可以分为以下几类:

  • 对象生命周期管理
  • 对象表示与格式化
  • 运算符重载
  • 属性访问控制
  • 容器与迭代器
  • 同(异)步上下文管理
  • 可调用对象与描述符
  • 一些不常见但很有用的用法

下面将针对常用的场景进行展开分析

魔法方法在不同场景中的分析与实践

场景1:对象生命周期管理

下列这些魔法方法控制对象的创建,初始化和销毁。

  • __new__(cls, *args, **kwargs):控制对象的创建,是真正的构造器,是对象创建过程中的第一个方法(静态方法),它在__init__之前调用。通常情况下,是不需要直接使用__new__,但如果你需要控制对象的创建过程(如:实现单例模式或自定义元类),可以重写它。
  • __init__(self, *args, **kwargs):是初始化器,初始化对象的,对象在内存已经分配,即self已经存在,接收参数并设置初始状态,它在每次创建对象时被自动调用,几乎每个类都会实现它。
  • __del__(self):定义对象被垃圾回收时的行为。需要注意的是,最好谨慎使用,如果使用不当,可能引发资源管理问题。因为该方法不保证一定被调用,其主要依赖垃圾回收机制的策略。
1
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
28
class Resource:
def __new__(cls, *args, **kwargs):
"""在__init__之前调用"""
print("__new__: 创建对象实例")
instance = super().__new__(cls)
return instance

def __init__(self, name):
"""设置对象初始状态"""
print(f"__init__: 初始化资源 '{name}'")
self.name = name
self._resource = self._acquire_resource()

def __del__(self):
"""对象被垃圾回收时调用"""
print(f"__del__: 清理资源 '{self.name}'")
self._release_resource()

def _acquire_resource(self):
print("获取系统资源...")
return "模拟的资源句柄"

def _release_resource(self):
print("释放系统资源...")

# 使用示例
resource = Resource("数据库连接")
# 当对象被垃圾回收时,__del__会自动调用

场景2:对象表示与格式化

下列这些魔法方法控制对象的字符串表示,用于调试或展示。

  • __str__(self):为最终用户设计,要求可读性好,定义str(obj)print(obj)时的行为,也就是常被用于输出或打印,这个方法应该返回一个易于理解的字符串,用于展示对象的“外观”。
  • __repr__(self):为开发者设计,要求明确无误,定义repr(obj)或交互环境中对象的表示,通常用于开发者调试,返回详细信息,它的目标是生成一个可以通过eval()恢复的字符串(即反向构造对象)。
  • __format__(self, format_spec):定义format(obj, spec)f-string 中的格式化的行为,支持自定义格式化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def __repr__(self):
"""这是对象的官方字符串表示,主要给开发者看"""
return f"Student(name='{self.name}', age={self.age})"

def __str__(self):
"""这是对象的非正式字符串表示,主要给用户看"""
return f"{self.name}{self.age}岁"

ssw = Student("BluesSen", 32)
print(repr(tom)) # Student(name='BluesSen', age=32)
print(str(tom)) # ssw,32岁
print(tom) # ssw,32岁

场景3:属性访问控制

下列这些魔法方法控制对象的访问、设置和删除行为,适合实现动态属性或代理模式。

  • __getattr__(self, name):当访问不存在的属性时调用,适合实现灵活的属性延迟加载或代理模式,比如实现一个ORM模型。
  • __setattr__(self, name, value):设置属性时调用,但需要注意避免无限递归的问题,实际开发中建议使用 super().__setattr__ 来避免递归。
  • __delattr__(self, name):删除属性时调用。
  • __getattribute__(self, name):无条件访问任何属性时都会调用。极其强大,也极其危险,容易引发无限递归(必须通过super().__getattribute__来访问属性)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LazyObject:
def __init__(self):
self.exists = 5

def __getattr__(self, name):
"""只有在找不到已存在的属性时才会调用"""
print(f"__getattr__被调用,尝试获取不存在的属性: '{name}'")
value = f"这是动态生成的属性 '{name}' 的值"
setattr(self, name, value) # 将其设置为实例属性,下次就不用走__getattr__了
return value

# 谨慎使用 __getattribute__,容易导致无限递归!
# def __getattribute__(self, name):
# print(f"__getattribute__被调用,获取属性: '{name}'")
# # 必须通过super()来避免递归
# return super().__getattribute__(name)

obj = LazyObject()
print(obj.exists) # 输出:5 (正常访问,不会触发__getattr__)
print(obj.foo) # 输出:__getattr__被调用... -> 这是动态生成的属性 'foo' 的值
print(obj.foo) # 输出:这是动态生成的属性 'foo' 的值 (第二次访问,foo已存在,不再触发__getattr__)

场景4:容器与迭代器

首先需要明确的什么是容器、可迭代对象、迭代器对象:

  • 容器:详见Python中的数据类型用法剖析:从底层实现到高效应用
  • 可迭代对象:实现 __iter__,返回迭代器对象(通常 return self
  • 迭代器对象:实现 __iter__ 和 __next__,返回下一个值,结束时抛出 StopIteration
    下列这些魔法方法让类支持容器操作(如in、索引)和迭代。
  • __len__(self):定义len(obj)的行为。
  • __getitem__(self, key):可以实现自定义对象的下标访问行为,但需要注意的是在实现切片支持时,__getitem__需要处理slice对象。
  • __setitem__(self, key, value):可以实现自定义对象的下标赋值行为。
  • __delitem__(self, key) :用于实现删除通过键或索引访问的元素的操作。
  • __iter__(self)__next__(self) :使对象可迭代(即支持for循环),这二者可与生成器结合优化内存。
  • __contains__(self, item) :定义 in 操作符的行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyList:
def __init__(self, items):
self.items = list(items)

def __len__(self):
return len(self.items)

def __getitem__(self, index):
return self.items[index]

def __iter__(self):
return iter(self.items)

def __contains__(self, item):
return item in self.items

ml = MyList([1, 2, 3])
print(len(ml)) # 3
print(ml[1]) # 2
print(2 in ml) # True
for x in ml:
print(x) # 1, 2, 3

场景5:同步-上下文管理

下列这些魔法方法让类支持with语句,用于资源管理。

  • __enter__(self):进入with时调用,返回上下文对象。
  • __exit__(self, exc_type, exc_value, traceback):退出with时调用,处理异常,需要提醒的是,该方法返回True时可抑制异常,也就是说异常会被“吞掉”,上下文管理器外的代码不会收到异常。这是一个强大但危险的特性。很适合事务处理,如果结合contextlib模块可简化上下文管理器的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Timer:
"""一个计时器上下文管理器"""
def __enter__(self):
import time
self.start = time.time()
return self # as 后面的变量得到的是这个返回值

def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.elapsed = time.time() - self.start
print(f"代码块运行耗时: {self.elapsed:.4f} 秒")
# 返回 False 表示不压制异常;返回 True 则异常会被吞掉,外部不会收到异常。
return False

# 使用上下文管理器
with Timer() as t:
# 模拟一些耗时操作
sum(i for i in range(1000000))

# 退出 with 块后会自动打印时间
# 并且我们还可以访问 t.elapsed 这个属性
print(f"外部访问耗时: {t.elapsed}秒")

场景6:异步-上下文管理

下列这些魔法方法必须定义在支持异步的类中。

  • __await__(self)(可等待对象):必须返回一个迭代器(通常通过 iter() 包装一个生成器),可用于实现自定义可等待对象(如 asyncio.Future)。
  • __alter__(self)(异步迭代器):返回异步迭代器对象,通常return self
  • __anext__:必须返回一个awaitable 对象,(通常是 coroutineTask)。
  • __aenter__(self)(异步上下文管理器):返回进入时的资源,常为await self.connect().
  • __aexit__(self, exc_type, exc_value, traceback):用于异步清理(如关闭连接),可处理异常
  • StopAsyncIteration 是异步迭代结束的信号(不要手动抛出,除非你控制迭代逻辑)。
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 自定义异步上下文管理器
import asyncio

class AsyncDatabase:
async def connect(self):
print("正在连接数据库...")
await asyncio.sleep(1)
print("数据库连接成功")

async def disconnect(self):
print("正在断开数据库...")
await asyncio.sleep(0.5)
print("数据库已断开")

# 异步上下文管理器
def __aenter__(self):
return self.connect() # 返回一个协程

def __aexit__(self, exc_type, exc_value, traceback):
return self.disconnect() # 返回一个协程

# 使用示例
async def main():
async with AsyncDatabase() as db:
print("数据库操作中...")

asyncio.run(main())

# 自定义异步迭代器
class AsyncCounter:
def __init__(self, limit):
self.limit = limit
self.current = 0

def __aiter__(self):
return self

async def __anext__(self):
if self.current < self.limit:
await asyncio.sleep(0.1) # 模拟异步操作
self.current += 1
return self.current
else:
raise StopAsyncIteration

async def main():
async for num in AsyncCounter(5):
print(num)

asyncio.run(main())

场景7:可调用对象

下列魔法方法让普通对象变为可调用对象,可用于实现函数式接口、装饰器类、回调等。

  • __call__(self, *args, **kwargs) :让类的实例像函数一样被调用,适合实现装饰器或函数式接口。
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
class Counter:
"""一个计数器,每次调用都会递增"""
def __init__(self, start=0):
self.count = start

def __call__(self):
"""使得实例可以像函数一样被调用:obj()"""
self.count += 1
print(f"当前计数: {self.count}")
return self.count

# 创建一个计数器实例
counter = Counter(10)
counter() # 输出:当前计数: 11
counter() # 输出:当前计数: 12
# 它是有状态的


class CallLogger:
"""记录函数调用信息的装饰器类"""
def __init__(self, func):
self.func = func
self.call_count = 0

def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"开始执行第 {self.call_count} 次调用: {self.func.__name__}")
result = self.func(*args, **kwargs)
print(f"函数 {self.func.__name__} 执行完毕")
return result

@CallLogger
def say_hello(name):
print(f"Hello, {name}!")

say_hello("Alice")
# 输出:
# 开始执行第 1 次调用: say_hello
# Hello, Alice!
# 函数 say_hello 执行完毕

场景8:描述符协议

下列这些魔法方法,用于创建描述符类,控制属性的访问,本质是属性代理。简单地说,它允许一个对象在作为另一个对象的属性时,自定义其获取、设置、删除行为。常被用于属性验证懒加载ORM 字段等场景。

  • __get__(self, instance, owner):获取属性值。
  • __set__(self, instance, value):设置属性值。
  • __delete__(self, instance) :输出属性。
  • __set_name__(self, owner, name):在类创建时设置描述符名称(Python3.6+)
    值得一提:它们是 @property@classmethod等装饰器的底层实现。在Django中被广泛使用。
    需要注意:描述符是实现了__get____set____delete__中至少一个方法的类。
1
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
28
29
30
31
32
33
class PositiveNumber:
"""一个描述符,确保数值是正数"""
def __init__(self, name):
self.name = name

def __get__(self, instance, owner):
# instance 是使用描述符的类的实例(如Rectangle的实例)
# owner 是使用描述符的类本身(如Rectangle类)
return instance.__dict__.get(self.name, 0)

def __set__(self, instance, value):
if value <= 0:
raise ValueError("值必须是正数")
# 将值存储在实例的 __dict__ 中,而不是描述符实例自身
instance.__dict__[self.name] = value

class Rectangle:
width = PositiveNumber('width') # 描述符实例
height = PositiveNumber('height') # 描述符实例

def __init__(self, w, h):
self.width = w # 触发 PositiveNumber.__set__
self.height = h # 触发 PositiveNumber.__set__

@property
def area(self):
return self.width * self.height # 触发 PositiveNumber.__get__

r = Rectangle(5, 3)
print(r.area) # 输出:15
r.width = 10 # 正常设置
print(r.area) # 输出:30
# r.width = -5 # 会抛出 ValueError: 值必须是正数

上述代码分析

  • 描述符PositiveNumber的实例作为类属性(widthheight)被赋值给Rectangle类。
  • 当对实例r的属性(如r.width)进行访问或赋值时,实际上被拦截,转而调用描述符的__get____set__方法。
  • @property本质上就是一个实现了__get____set__的描述符。
    总结:描述符是实现精细属性管理、ORM框架映射关系等高级功能的利器。

场景9:运算符重载

下列这些魔法方法允许自定义类支持Python的运算符(如:+-<等)。
(一) 实现算术运算符:

  • __add__(self, other):实现加法运算符 +
  • __sub__(self, other):实现减法运算符 -
  • __mul__(self, other):实现乘法运算符 *
  • __truediv__(self, other):实现真除法运算符 /
  • __floordiv__(self, other):实现整数除法运算符 //
  • __mod__(self, other):实现取模运算符 %
  • __pow__(self, other[, modulo]):实现幂运算符 **
  • __iadd__(self, other):实现复合运算符 +=
  • __isub__(self, other):实现复合运算符 -=
  • __imul__(self, other) :实现复合运算符 *=
    (二)实现位运算符(不常用)
  • __lshift__(self, other):实现左移位运算符 <<
  • __rshift__(self, other):实现右移位运算符 >>
  • __and__(self, other):实现按位与运算符 &
  • __xor__(self, other):实现按位异或运算符 ^
  • __or__(self, other):实现按位或运算符 |
    (三)实现比较运算符:
  • __lt__(self, other):实现小于运算符 <
  • __le__(self, other):实现小于等于运算符 <=
  • __eq__(self, other):实现等于运算符 ==
  • __ne__(self, other):实现不等于运算符 !=
  • __gt__(self, other):实现大于运算符 >
  • __ge__(self, other):实现大于等于运算符 >=
    **友情提醒:**实现比较运算符时,建议使用 functools.total_ordering 装饰器,只需定义 __eq____lt__ 即可自动生成其他比较方法。

(四)实现容器类型类似方法(如序列、映射等):

这些方法允许对象像列表或字典一样被操作。

  • __getitem__(self, key):实现通过键或索引获取元素,如 self[key]
  • __setitem__(self, key, value):实现通过键或索引设置元素,如 self[key] = value
  • __delitem__(self, key):实现通过键或索引删除元素,如 del self[key]
  • __contains__(self, item):实现成员测试运算符 in
  • __len__(self):实现内置函数 len(),返回容器的长度。
    由于方法太多,这里就简单的实现一个计算器功能吧
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 让对象参与数学运算,支持向量的加减法

class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

def __repr__(self):
return f"Vector({self.x}, {self.y})"

def __add__(self, other):
"""实现 v1 + v2 或 v1 + number"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
elif isinstance(other, (int, float)):
# 标量加法,x和y都加
return Vector(self.x + other, self.y + other)
else:
return NotImplemented # 告诉Python此操作不支持,让Python尝试 other.__radd__

def __radd__(self, other):
"""实现 number + v1(当数字在左边时调用)"""
# 向量加法满足交换律,所以直接调用 __add__
return self.__add__(other)

def __iadd__(self, other):
"""实现 v1 += v2 或 v1 += number(就地修改)"""
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
elif isinstance(other, (int, float)):
self.x += other
self.y += other
else:
return NotImplemented
return self # 就地运算必须返回self

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2) # 输出:Vector(4, 6) (调用 __add__)
print(v1 + 5) # 输出:Vector(6, 7) (调用 __add__)
print(5 + v1) # 输出:Vector(6, 7) (先尝试 int.__add__(5, v1) 失败,然后调用 v1.__radd__(5))
v1 += v2
print(v1) # 输出:Vector(4, 6) (调用 __iadd__,就地修改)

场景10:一些不常见但很有用的方法

  • __slots__ :限制类可添加的属性,优化内存和性能。
  • __class__ :获取或修改对象的类,动态性极强。
  • __copy__(self)copy.copy(obj),浅拷贝
  • __deepcopy__(self, memo)copy.deepcopy(obj),深拷贝
  • __getstate__(self)pickle 序列化时获取对象状态
  • _setstate__(self, state)pickle 反序列化时恢复状态
  • __dir__(self)dir(obj),自定义属性列表
  • __sizeof__(self)sys.getsizeof(obj),返回对象内存大小,
  • __hash__(self)hash(obj),用于哈希表(如字典键),
  • __instancecheck__(self, instance)__subclasscheck__(self, subclass) :自定义 isinstanceissubclass 行为,通常在元类中使用。

一些开箱即用的实践案例

案例1:实现一个像列表和字典一样的类

1
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
28
29
30
31
class Playlist:
"""一个简单的播放列表类"""
def __init__(self, *songs):
self._songs = list(songs)

def __len__(self):
"""实现 len(obj)"""
return len(self._songs)

def __getitem__(self, index):
"""实现 obj[index],还支持切片!"""
# 直接返回内部列表的对应项,巧妙利用列表的切片功能
return self._songs[index]

def __contains__(self, song):
"""实现 item in obj"""
return song in self._songs

def __iter__(self):
"""实现迭代,支持 for 循环"""
return iter(self._songs)

my_playlist = Playlist("Song A", "Song B", "Song C")

print(len(my_playlist)) # 输出:3 (调用 __len__)
print(my_playlist[1]) # 输出:Song B (调用 __getitem__)
print(my_playlist[0:2]) # 输出:['Song A', 'Song B'] (切片也有效!)
print("Song B" in my_playlist) # 输出:True (调用 __contains__)
for song in my_playlist: # 调用 __iter__
print(song) # 依次输出 Song A, Song B, Song C

案例2:实现一个支持对象支持比较类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def __repr__(self):
return f"Student(name='{self.name}', age={self.age})"

def __eq__(self, other):
"""定义相等性:姓名和年龄都相同就认为是同一个学生"""
if not isinstance(other, Student):
return False
return self.name == other.name and self.age == other.age

def __hash__(self):
"""如果两个对象相等,它们的hash值必须相同"""
return hash((self.name, self.age))

tom1 = Student("Tom", 20)
tom2 = Student("Tom", 20)
print(tom1 == tom2) # True

# 现在可以把学生对象放进set或用作dict的key了
students = {tom1, tom2}
print(len(students)) # 1,因为两个Tom被认为是同一个

案:3:实现一个缓存器

1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from functools import wraps
from collections import OrderedDict
import time

class LRUCache:
"""LRU缓存实现"""

def __init__(self, maxsize=128, ttl=None):
self.maxsize = maxsize
self.ttl = ttl
self.cache = OrderedDict()
self.times = {} if ttl else None

def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
key = self._make_key(args, kwargs)

# 检查缓存
if key in self.cache:
if self._is_valid(key):
# 移到末尾(最近使用)
self.cache.move_to_end(key)
return self.cache[key]
else:
# 过期,删除
self._delete_key(key)

# 计算结果
result = func(*args, **kwargs)

# 存储结果
self._store_result(key, result)

return result

wrapper.cache_info = self.cache_info
wrapper.cache_clear = self.cache_clear
return wrapper

def _make_key(self, args, kwargs):
"""创建缓存键"""
key = args
if kwargs:
key += tuple(sorted(kwargs.items()))
return key

def _is_valid(self, key):
"""检查缓存是否有效"""
if self.ttl is None:
return True
return time.time() - self.times[key] < self.ttl

def _delete_key(self, key):
"""删除缓存项"""
del self.cache[key]
if self.times:
del self.times[key]

def _store_result(self, key, result):
"""存储缓存结果"""
# 检查容量
if len(self.cache) >= self.maxsize:
# 删除最老的项
oldest_key = next(iter(self.cache))
self._delete_key(oldest_key)

self.cache[key] = result
if self.times:
self.times[key] = time.time()

def cache_info(self):
"""缓存信息"""
return f"Cache size: {len(self.cache)}/{self.maxsize}"

def cache_clear(self):
"""清空缓存"""
self.cache.clear()
if self.times:
self.times.clear()

# 使用示例
@LRUCache(maxsize=100, ttl=300)
def expensive_function(x, y):
time.sleep(1) # 模拟耗时操作
return x * y + x ** 2

案例4:实现一个配置管理器

1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class Config:
"""支持点号访问和字典访问的配置管理器"""

def __init__(self, **kwargs):
self._data = {}
for key, value in kwargs.items():
self[key] = value

def __getitem__(self, key):
"""字典式访问:config['database_url']"""
return self._data[key]

def __setitem__(self, key, value):
"""字典式赋值:config['database_url'] = 'sqlite:///app.db'"""
self._data[key] = value

def __delitem__(self, key):
"""删除配置项:del config['database_url']"""
del self._data[key]

def __contains__(self, key):
"""检查是否存在:'database_url' in config"""
return key in self._data

def __len__(self):
"""配置项数量:len(config)"""
return len(self._data)

def __iter__(self):
"""迭代配置项:for key in config"""
return iter(self._data)

def __getattr__(self, name):
"""点号访问:config.database_url"""
try:
return self._data[name]
except KeyError:
raise AttributeError(f"配置项 '{name}' 不存在")

def __setattr__(self, name, value):
"""点号赋值:config.database_url = 'sqlite:///app.db'"""
if name.startswith('_'):
# 私有属性正常处理
super().__setattr__(name, value)
else:
# 公共属性存储到配置中
if not hasattr(self, '_data'):
super().__setattr__('_data', {})
self._data[name] = value

def __str__(self):
items = [f"{k}={v!r}" for k, v in self._data.items()]
return f"Config({', '.join(items)})"

def keys(self):
return self._data.keys()

def values(self):
return self._data.values()

def items(self):
return self._data.items()

def get(self, key, default=None):
return self._data.get(key, default)

def update(self, **kwargs):
for key, value in kwargs.items():
self[key] = value

# 使用示例
config = Config(
database_url="sqlite:///app.db",
debug=True,
max_connections=100
)

# 多种访问方式
print(config['database_url']) # sqlite:///app.db
print(config.database_url) # sqlite:///app.db
print('debug' in config) # True

# 动态添加配置
config.secret_key = "my-secret-key"
config['cache_timeout'] = 300

# 迭代配置
for key in config:
print(f"{key}: {config[key]}")

print(f"共有 {len(config)} 个配置项")

案例5:实现一个单例类

1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class SingletonMeta(type):
"""单例模式元类"""
_instances = {}
_lock = {}

def __new__(mcs, name, bases, namespace, **kwargs):
"""创建新类时调用"""
print(f"创建类 {name}")
cls = super().__new__(mcs, name, bases, namespace)
# 为每个类创建一个锁
mcs._lock[cls] = __import__('threading').Lock()
return cls

def __call__(cls, *args, **kwargs):
"""创建实例时调用"""
if cls not in cls._instances:
with cls._lock[cls]:
# 双重检查
if cls not in cls._instances:
print(f"创建 {cls.__name__} 的第一个实例")
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
else:
print(f"返回 {cls.__name__} 的现有实例")
else:
print(f"返回 {cls.__name__} 的现有实例")

return cls._instances[cls]

class DatabaseConnection(metaclass=SingletonMeta):
"""数据库连接类(单例)"""

def __init__(self, host="localhost", port=5432):
if hasattr(self, '_initialized'):
return

self.host = host
self.port = port
self.connected = False
self._initialized = True
print(f"初始化数据库连接: {host}:{port}")

def connect(self):
if not self.connected:
print(f"连接到数据库 {self.host}:{self.port}")
self.connected = True
else:
print("已经连接到数据库")

def query(self, sql):
if not self.connected:
self.connect()
print(f"执行查询: {sql}")
return f"查询结果 for '{sql}'"

# 测试单例
print("=== 创建数据库连接实例 ===")
db1 = DatabaseConnection("192.168.1.100")
db2 = DatabaseConnection("localhost") # 参数被忽略
db3 = DatabaseConnection()

print(f"db1 is db2: {db1 is db2}") # True
print(f"db1.host: {db1.host}") # 192.168.1.100

db1.connect()
db2.query("SELECT * FROM users") # 使用同一个连接

一些好的实践与建议

  • 开发调试阶段
    • __repr__ 比 __str__ 更重要,调试时非常有用。
  • 建议使用
    • 实现 __eq__ 时,通常也要实现 __hash__(除非对象是可变的)。
    • 使用 @total_ordering 装饰器可以减少比较方法的编写量(只需实现 __eq__ 和一个如 __lt__)。
    • 在设计类时最好做到总是检查参数类型,使用NotImplemented而不是抛出异常。
    • 在设计类时考虑使用@dataclass@attrs来减少样板代码
    • 魔法方法调用有开销,在性能敏感的循环中,直接调用方法可能比操作符更快。
  • 谨慎使用:
    • __getattribute____setattr__中直接使用self.attr会导致无限递归。必须使用super()或直接操作__dict__

总结一下

类别 主要方法 应用场景
对象生命周期 __new__, __init__, __del__ 对象创建、初始化、销毁
对象表示与格式化 __str__, __repr__, __format__ 字符串表示、调试输出
运算符重载 __add__, __eq__, __lt__ 数学运算、比较操作
属性访问控制 __getattr__, __setattr__, __getattribute__ 动态属性、代理模式
容器与迭代器 __len__, __getitem__, __iter__ 序列、映射、迭代
上下文管理 __enter__, __exit__ 资源管理、with语句
可调用对象 __call__ 函数式接口、装饰器类
描述符协议 __get__, __set__, __delete__ 属性验证、ORM字段
异步编程 __aenter__, __aexit__, __anext__ 异步上下文、迭代

Python的魔法方法不是“黑魔法”,而是一套精心设计的、用于扩展语言能力的协议系统。作为Python开发者,深入理解并恰当地运用它们,能够让我们设计出API更清晰、更符合Python风格、更强大的代码库。

然而,常言道:能力越大,责任越大……

魔法方法虽然功能强大,但滥用魔法方法会让代码变得难以理解和调试。我们应该始终遵循“明确胜于隐晦”的Python之禅,只在真正需要让对象模拟内置行为或实现特定协议时才使用它们。


本站由 BluesSen 使用 Stellar 1.33.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站总访问量