
文章目录
- 📖 开篇导读
- 🎯 学习目标
- 📚 知识点理论精讲
- 💻 代码案例实操
- ⚠️ 易错点避坑总结
- 📝 课后实战练习题
- 🧠 知识点思维导图总结
- 🔜 下节课预告
- 🔗《50节课 Python 从入门到精通》系列课程导航
📖 开篇导读
在前面的课程中,我们已经学习了类的基本定义、属性、方法、封装、继承、多态等面向对象的核心知识。但Python的类还有一些特殊的方法,它们的名称前后各有两个下划线(如__init__、__str__、__len__),被称为魔术方法(Magic Methods)或双下方法(Dunder Methods,即Double Underscore的缩写)。
这些魔术方法让你能够定义类的行为,使其与Python的内置功能无缝集成。例如:通过__str__控制print(obj)的输出;通过__len__让自定义容器支持len(obj);通过__add__让对象支持+运算符;通过__getitem__让对象支持索引访问……掌握这些方法,你就能写出“Pythonic”的类,像内置类型一样优雅。
💡 工作场景:在真实开发中,几乎每个类都会用到至少一两个魔术方法:
__init__构造对象,__str__提供可读的打印信息,__repr__提供调试用的准确表示,__enter__和__exit__实现上下文管理器(with语句),__call__让对象变成可调用的函数等。
本课将系统讲解最常用的魔术方法,通过大量案例让你理解它们的含义和应用场景。学完本课,你将能够设计出与Python语言完美融合的类。
🎯 学习目标
| 目标编号 | 具体掌握内容 | 对应面试/工作价值 |
|---|---|---|
| 1️⃣ | 掌握对象表示相关魔术方法:__str__和__repr__的区别及应用 | 让打印输出更友好 |
| 2️⃣ | 掌握容器模拟方法:__len__、__getitem__、__setitem__、__delitem__ | 实现自定义序列/映射 |
| 3️⃣ | 掌握运算符重载:__add__、__eq__、__lt__等 | 让类支持数学运算和比较 |
| 4️⃣ | 掌握可调用对象:__call__方法 | 创建带状态的函数 |
| 5️⃣ | 掌握上下文管理器:__enter__和__exit__ | 支持with语句 |
| 6️⃣ | 掌握属性访问控制:__getattr__、__setattr__ | 动态属性、代理模式 |
🔥 面试考点:“
__str__和__repr__的区别?”“如何让自定义类支持len()和索引?”“__call__的作用?”“什么是上下文管理器?如何实现?”
📚 知识点理论精讲
一、对象表示:__str__ 与 __repr__
1.1 基本概念
__str__:面向用户的可读性表示,供print()和str()调用。__repr__:面向开发者的准确表示,通常应该是可用来重新创建该对象的字符串,供交互式环境和repr()调用。如果没有定义__str__,Python会使用__repr__作为后备。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
def __repr__(self):
return f"Person('{self.name}', {self.age})"
p = Person("张三", 25)
print(p) # 调用 __str__
print(repr(p)) # 调用 __repr__
最佳实践:
__repr__尽量返回能够重新创建对象的表达式字符串。__str__返回简洁友好的描述。
💡 工作应用:调试时,交互式环境默认使用
__repr__;日志记录中通常使用__str__。
二、容器模拟:__len__、__getitem__、__setitem__、__delitem__
实现这些方法可以让自定义类表现得像列表或字典。
2.1 __len__ 和 __getitem__ 实现序列协议
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]
ml = MyList([1,2,3])
print(len(ml)) # 3
print(ml[1]) # 2
for item in ml: # 可迭代
print(item)
2.2 __setitem__ 和 __delitem__ 支持修改和删除
class MutableList(MyList):
def __setitem__(self, index, value):
self._items[index] = value
def __delitem__(self, index):
del self._items[index]
ml = MutableList([1,2,3])
ml[1] = 99
del ml[0]
print(list(ml)) # [99,3]
2.3 实现映射(类似字典)
class DictLike:
def __init__(self):
self._data = {}
def __getitem__(self, key):
return self._data[key]
def __setitem__(self, key, value):
self._data[key] = value
def __delitem__(self, key):
del self._data[key]
def __len__(self):
return len(self._data)
d = DictLike()
d["name"] = "张三"
print(d["name"]) # 张三
print(len(d)) # 1
三、运算符重载:__add__、__sub__、__eq__等
通过魔术方法,可以让自定义对象支持算术运算和比较。
3.1 算术运算符
| 运算符 | 魔术方法 |
|---|---|
+ | __add__ |
- | __sub__ |
* | __mul__ |
/ | __truediv__ |
// | __floordiv__ |
% | __mod__ |
** | __pow__ |
3.2 比较运算符
| 运算符 | 魔术方法 |
|---|---|
== | __eq__ |
!= | __ne__ |
< | __lt__ |
<= | __le__ |
> | __gt__ |
>= | __ge__ |
3.3 示例:二维向量
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 __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1,2)
v2 = Vector(3,4)
v3 = v1 + v2
print(v3) # Vector(4,6)
print(v1 == Vector(1,2)) # True
四、可调用对象:__call__
如果一个类定义了__call__方法,其实例就可以像函数一样被调用。
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(callable(double)) # True
应用场景:实现带状态的函数,替代闭包。
五、上下文管理器:__enter__ 和 __exit__
用于实现with语句,自动管理资源(如文件、锁、数据库连接)。
class ManagedFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
# 如果返回 True,异常会被抑制;返回 False 则向上传播
return False
with ManagedFile("test.txt", "w") as f:
f.write("Hello")
六、属性访问控制:__getattr__、__setattr__、__delattr__
__getattr__(self, name):当通过正常方式找不到属性时调用。__getattribute__(self, name):每次访问属性都会调用(慎用,易递归)。__setattr__(self, name, value):每次设置属性时调用。__delattr__(self, name):删除属性时调用。
6.1 __getattr__ 实现动态属性
class DynamicPerson:
def __init__(self, name):
self.name = name
def __getattr__(self, name):
return f"{name} is not defined"
p = DynamicPerson("张三")
print(p.age) # age is not defined
6.2 __setattr__ 实现属性验证
class Positive:
def __init__(self, value):
self.value = value # 会调用 __setattr__
def __setattr__(self, name, value):
if name == "value" and value < 0:
raise ValueError("value must be positive")
super().__setattr__(name, value) # 避免递归
七、其他常用魔术方法
| 魔术方法 | 作用 |
|---|---|
__new__(cls, ...) | 控制对象创建,返回实例 |
__del__(self) | 对象被销毁前调用(析构) |
__hash__(self) | 定义哈希值,用于字典键和集合 |
__bool__(self) | 定义布尔测试,if obj调用 |
__iter__(self) | 返回迭代器,用于for循环 |
__next__(self) | 迭代器迭代时返回下一个值 |
__contains__(self, item) | 支持in运算符 |
💻 代码案例实操
案例1:__str__ 与 __repr__ 对比
"""
str_repr_demo.py
演示__str__和__repr__的区别
"""
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
"""用户友好的显示"""
return f"《{self.title}》 by {self.author}"
def __repr__(self):
"""开发者友好的显示,可用于重建对象"""
return f"Book('{self.title}', '{self.author}', {self.pages})"
book = Book("Python编程", "张三", 350)
print(str(book)) # 《Python编程》 by 张三
print(repr(book)) # Book('Python编程', '张三', 350)
# 列表中的对象会使用 __repr__
print([book]) # [Book('Python编程', '张三', 350)]
案例2:自定义序列(可迭代、支持切片)
"""
custom_sequence.py
实现一个支持切片和迭代的自定义序列
"""
class MyRange:
"""模拟内置range,但支持更多功能"""
def __init__(self, start, stop=None, step=1):
if stop is None:
start, stop = 0, start
self.start = start
self.stop = stop
self.step = step
def __len__(self):
"""计算元素个数"""
if self.step > 0:
if self.start >= self.stop:
return 0
return (self.stop - self.start - 1) // self.step + 1
else:
if self.start <= self.stop:
return 0
return (self.start - self.stop - 1) // (-self.step) + 1
def __getitem__(self, index):
"""支持索引和切片"""
if isinstance(index, slice):
# 处理切片
start, stop, step = index.indices(len(self))
return [self[i] for i in range(start, stop, step)]
else:
# 处理整数索引
if index < 0:
index += len(self)
if index < 0 or index >= len(self):
raise IndexError("index out of range")
return self.start + index * self.step
def __iter__(self):
"""支持迭代"""
current = self.start
while (self.step > 0 and current < self.stop) or (self.step < 0 and current > self.stop):
yield current
current += self.step
def __repr__(self):
return f"MyRange({self.start}, {self.stop}, {self.step})"
# 测试
r = MyRange(1, 10, 2)
print(len(r)) # 5
print(list(r)) # [1,3,5,7,9]
print(r[2]) # 5
print(r[1:4]) # [3,5,7]
案例3:二维向量(运算符重载)
"""
vector_ops.py
演示算术和比较运算符重载
"""
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""数乘,注意标量应在右侧"""
if isinstance(scalar, (int, float)):
return Vector2D(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""左乘法:scalar * vector"""
return self.__mul__(scalar)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __abs__(self):
return (self.x**2 + self.y**2) ** 0.5
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
v1 = Vector2D(1,2)
v2 = Vector2D(3,4)
v3 = v1 + v2
print(v3) # Vector2D(4,6)
v4 = 2 * v1
print(v4) # Vector2D(2,4)
print(abs(v1)) # 2.236...
print(v1 == Vector2D(1,2)) # True
案例4:可调用对象实现累加器
"""
callable_accumulator.py
使用__call__实现带状态的累加器
"""
class Accumulator:
def __init__(self, start=0):
self.value = start
def __call__(self, increment=1):
self.value += increment
return self.value
def reset(self):
self.value = 0
acc = Accumulator(10)
print(acc()) # 11
print(acc(5)) # 16
print(acc()) # 17
acc.reset()
print(acc()) # 1
# 作为装饰器使用
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 被调用第 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello")
say_hello()
say_hello()
案例5:上下文管理器实现计时器
"""
context_timer.py
使用__enter__和__exit__实现计时上下文管理器
"""
import time
class Timer:
def __init__(self, name="Block"):
self.name = name
def __enter__(self):
self.start = time.perf_counter()
return self # 可以返回对象供with内部使用
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.perf_counter()
elapsed = self.end - self.start
print(f"{self.name} 耗时: {elapsed:.4f}秒")
# 如果发生异常,不抑制(返回False)
return False
def elapsed(self):
return self.end - self.start
# 使用
with Timer("循环1") as t:
time.sleep(0.5)
# 嵌套
with Timer("外层"):
time.sleep(0.2)
with Timer("内层"):
time.sleep(0.3)
案例6:动态属性代理(__getattr__和__setattr__)
"""
dynamic_proxy.py
实现一个代理类,动态转发属性
"""
class Proxy:
def __init__(self, obj):
self._obj = obj
def __getattr__(self, name):
print(f"获取属性: {name}")
return getattr(self._obj, name)
def __setattr__(self, name, value):
if name == '_obj':
super().__setattr__(name, value)
else:
print(f"设置属性: {name} = {value}")
setattr(self._obj, name, value)
def __delattr__(self, name):
print(f"删除属性: {name}")
delattr(self._obj, name)
# 使用
class Data:
def __init__(self):
self.value = 100
self.name = "data"
d = Data()
p = Proxy(d)
print(p.value) # 获取属性: value -> 100
p.name = "new" # 设置属性: name = new
print(d.name) # new
del p.name # 删除属性: name
案例7:实现一个类似字典但带默认值的类(综合)
"""
default_dict_like.py
模拟collections.defaultdict,使用多个魔术方法
"""
class DefaultDict:
def __init__(self, default_factory=None):
self._data = {}
self.default_factory = default_factory
def __getitem__(self, key):
if key in self._data:
return self._data[key]
elif self.default_factory is not None:
value = self.default_factory()
self._data[key] = value
return value
else:
raise KeyError(key)
def __setitem__(self, key, value):
self._data[key] = value
def __delitem__(self, key):
del self._data[key]
def __contains__(self, key):
return key in self._data
def __len__(self):
return len(self._data)
def __iter__(self):
return iter(self._data)
def __repr__(self):
return f"DefaultDict({self._data})"
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
# 使用
d = DefaultDict(list)
d["fruits"].append("apple")
d["fruits"].append("banana")
d["numbers"].append(1)
print(d["fruits"]) # ['apple', 'banana']
print(d["unknown"]) # [](自动创建空列表)
print(len(d)) # 3
print("fruits" in d) # True
for key in d:
print(key, d[key])
⚠️ 易错点避坑总结
| 序号 | 坑点描述 | 后果 | 解决方案 |
|---|---|---|---|
| 1 | __getattr__和__getattribute__混淆 | __getattribute__每次访问都调用,易导致无限递归 | 一般重写__getattr__即可;若需__getattribute__,注意调用super() |
| 2 | 在__setattr__中直接赋值self.name = value | 无限递归,栈溢出 | 使用super().__setattr__(name, value) |
| 3 | 忘记实现__iter__或__getitem__导致对象不迭代 | for循环无效 | 至少实现__getitem__或__iter__ |
| 4 | 运算符重载中返回NotImplemented而不是None | 可能导致运算失败 | 当不支持操作数类型时返回NotImplemented |
| 5 | 上下文管理器的__exit__中忽略异常参数 | 异常被抑制或无法处理 | 根据exc_type决定是否处理,返回True抑制 |
| 6 | __repr__不够准确,无法重建对象 | 调试困难 | 尽量返回ClassName(...)格式 |
| 7 | __hash__和__eq__同时定义时,若对象可变则不应定义__hash__ | 放入字典后修改属性导致无法找到 | 可变对象不定义__hash__(不可哈希) |
| 8 | 在__new__中忘记返回实例 | __init__不被调用,返回None | 总是返回super().__new__(cls) |
| 9 | __del__中引用全局变量可能已被销毁 | 异常 | 避免在__del__中依赖全局变量 |
| 10 | 切片操作只处理整数索引,未处理slice对象 | 不支持切片 | 检查isinstance(index, slice) |
📝 课后实战练习题
第1题:__str__和__repr__
定义一个Money类,属性amount(浮点数)、currency(货币单位,如"CNY")。实现__str__返回格式"<货币符号>金额"(如"¥100.00"),__repr__返回"Money(amount, currency)"。创建实例测试。
第2题:实现不可变二维点
定义Point类,属性x和y为只读(通过@property),实现__add__、__sub__、__eq__、__repr__。创建点进行加减和比较。
第3题:可调用类实现斐波那契数列
定义Fibonacci类,实例可调用,每次调用返回下一个斐波那契数。例如fib = Fibonacci(); print(fib()); print(fib()) 输出0,1,1,2,…。支持重置。
第4题:实现上下文管理器@timed
实现一个装饰器@timed,它使用Timer上下文管理器,在函数执行前后打印执行时间。要求不修改原函数。
第5题:自定义序列ReversedList
实现ReversedList类,接受一个可迭代对象,在内部存储正向列表。但索引访问和迭代时返回逆向顺序。实现__len__、__getitem__、__iter__。
第6题:属性代理验证
定义Validated类,在__setattr__中验证属性名不能以_开头,且属性值不能为None。提示:使用super().__setattr__。
第7题:模拟defaultdict(已做案例,扩展)
在案例7的基础上,增加keys()、values()、items()方法,返回视图或列表。并支持update方法。
🧠 知识点思维导图总结
🔜 下节课预告
魔术方法让你能够控制类的行为,使其更加Pythonic。下一节课我们将学习设计模式入门,了解单例模式、工厂模式等常见设计模式及其在Python中的实现。
第28课:设计模式入门:单例模式、工厂模式Python极简实现
内容包括:
- 设计模式的概念和分类
- 单例模式(Singleton)的多种实现方式
- 工厂模式(简单工厂、工厂方法、抽象工厂)
- 实战:数据库连接池、日志器、图形工厂
设计模式是面向对象设计的经验总结,掌握它们能让你的代码更加优雅、可维护。
🌟 学习鼓励:魔术方法是Python语言的精髓之一。掌握它们,你就能写出与内置类型无缝衔接的类。不要试图一次记住所有魔术方法,先掌握最常用的
__str__、__repr__、__len__、__getitem__、__call__、__enter__/__exit__,再根据需要查阅其他方法。动手完成练习,让魔术方法成为你的肌肉记忆。
🔗《50节课 Python 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~


被折叠的 条评论
为什么被折叠?



