Python 魔术方法完全指南
本文档面向零基础新手,目标是让你真正理解:
- 魔术方法是什么,为什么叫”魔术”
- 对象的创建与销毁:
__new__、__init__、__del__ - 字符串表示:
__str__、__repr__、__format__ - 比较运算符:
__eq__、__lt__、__le__等 - 算术运算符:
__add__、__mul__、__radd__等 - 容器协议:
__len__、__getitem__、__iter__、__contains__等 - 可调用对象:
__call__ - 属性访问控制:
__getattr__、__setattr__、__getattribute__ - 上下文管理器:
__enter__、__exit__ - 布尔与哈希:
__bool__、__hash__ - 类型转换:
__int__、__float__、__bytes__ - 内存优化:
__slots__
配有大量可运行示例,全部从最基础讲起。
第一部分:魔术方法是什么?
1.1 什么是魔术方法
魔术方法(Magic Methods) 是 Python 中以双下划线开头和结尾的特殊方法,例如 __init__、__str__、__add__。
它们也叫 双下划线方法(Dunder Methods,dunder = double underscore)。
# 你每天都在用魔术方法,只是不知道而已:
a = [1, 2, 3]
len(a) # 实际调用:a.__len__()
a[0] # 实际调用:a.__getitem__(0)
a + [4, 5] # 实际调用:a.__add__([4, 5])
1 in a # 实际调用:a.__contains__(1)
for x in a: ... # 实际调用:a.__iter__()
str(a) # 实际调用:a.__str__()
print(a) # 实际调用:a.__repr__()
魔术方法的核心作用:让你自定义的类,能像内置类型一样使用内置语法。
1.2 不用魔术方法 vs 用魔术方法
# 不用魔术方法:需要记住各种方法名
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 to_string(self): # 自定义方法
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1.add(v2) # 只能用 .add()
print(v3.to_string()) # 只能用 .to_string()
# Vector(4, 6)
# 用魔术方法:和内置类型完全一样的使用体验
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 __str__(self): # 支持 print()
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # 就像操作数字一样!
print(v3) # 直接 print!
# Vector(4, 6)
第二部分:对象的生命周期
2.1 init——初始化对象(最常用)
__init__ 是创建对象时自动调用的初始化方法:
class Student:
def __init__(self, name, age, score=0):
"""
self:指向新创建的对象(自动传入,不用手动传)
name、age、score:初始化参数
"""
self.name = name # 给对象的属性赋值
self.age = age
self.score = score
print(f"[初始化] 创建了学生对象:{name}")
def study(self):
self.score += 10
print(f"{self.name} 学习了!成绩:{self.score}")
# 创建对象时,自动调用 __init__
s1 = Student("张三", 18)
s2 = Student("李四", 20, score=85)
s1.study()
print(s2.score) # 85
输出:
[初始化] 创建了学生对象:张三
[初始化] 创建了学生对象:李四
张三 学习了!成绩:10
85
2.2 new——创建对象(比 init 更底层)
__new__ 在 __init__ 之前调用,负责创建并返回对象。__init__ 只是初始化它。
class MyClass:
def __new__(cls, *args, **kwargs):
"""
cls:类本身(不是实例)
必须返回一个实例,否则 __init__ 不会被调用
"""
print(f"[__new__] 正在创建 {cls.__name__} 的实例")
instance = super().__new__(cls) # 调用父类的 __new__ 创建实例
return instance
def __init__(self, value):
print(f"[__init__] 正在初始化,value={value}")
self.value = value
obj = MyClass(42)
print(f"obj.value = {obj.value}")
输出:
[__new__] 正在创建 MyClass 的实例
[__init__] 正在初始化,value=42
obj.value = 42
__new__ 的实用场景——实现单例模式:
class Singleton:
"""单例类:无论创建多少次,都返回同一个对象"""
_instance = None # 类变量,保存唯一实例
def __new__(cls):
if cls._instance is None:
print("第一次创建实例")
cls._instance = super().__new__(cls)
else:
print("已存在实例,直接返回")
return cls._instance
def __init__(self):
pass
a = Singleton() # 第一次创建实例
b = Singleton() # 已存在实例,直接返回
c = Singleton() # 已存在实例,直接返回
print(a is b) # True(是同一个对象!)
print(b is c) # True
print(id(a), id(b), id(c)) # 三个 id 完全相同
2.3 del——析构(对象被销毁时调用)
__del__ 在对象被垃圾回收时自动调用(不可靠,不要依赖它来做重要清理):
class Resource:
def __init__(self, name):
self.name = name
print(f"[资源] 已打开:{self.name}")
def __del__(self):
print(f"[资源] 已释放:{self.name}")
# 对象超出作用域时被销毁
def use_resource():
r = Resource("数据库连接")
print(" 正在使用资源...")
# 函数结束时,r 被销毁,__del__ 被调用
use_resource()
print("函数已返回")
# 手动删除
r2 = Resource("文件句柄")
del r2 # 立即删除,触发 __del__
print("r2 已被删除")
输出:
[资源] 已打开:数据库连接
正在使用资源...
[资源] 已释放:数据库连接
函数已返回
[资源] 已打开:文件句柄
[资源] 已释放:文件句柄
r2 已被删除
警告: 不要依赖
__del__做资源清理(如关闭文件、数据库)。应该用with语句(__enter__/__exit__)来保证资源释放。
第三部分:字符串表示
3.1 str 和 repr 的区别
这是新手最常困惑的区别之一:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __str__(self):
"""
给"人类"看的,友好的字符串表示
被 print()、str() 调用
"""
return f"{self.celsius}°C"
def __repr__(self):
"""
给"开发者"看的,完整的技术表示
被 repr()、交互式解释器调用
在没有 __str__ 时,print() 也会用 __repr__
"""
return f"Temperature(celsius={self.celsius})"
t = Temperature(25)
print(t) # 调用 __str__:25°C
print(str(t)) # 调用 __str__:25°C
print(repr(t)) # 调用 __repr__:Temperature(celsius=25)
# 在列表中:用 __repr__
temps = [Temperature(20), Temperature(25), Temperature(30)]
print(temps) # [Temperature(celsius=20), Temperature(celsius=25), Temperature(celsius=30)]
对比表:
__str__ |
__repr__ |
|
|---|---|---|
| 目标受众 | 最终用户 | 开发者 |
| 调用方式 | print(obj)、str(obj) |
repr(obj)、交互式解释器 |
| 要求 | 好看易读 | 准确,最好能重建对象 |
| 如果没有定义 | 退回用 __repr__ |
用默认(内存地址) |
最佳实践:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
# 符合"能重建对象"的标准:eval(repr(obj)) == obj
return f"Point({self.x!r}, {self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
print(str(p)) # (3, 4)
print(repr(p)) # Point(3, 4)
# eval(repr(p)) 可以重建对象
p2 = eval(repr(p))
print(p2.x, p2.y) # 3 4
3.2 只定义 repr 不定义 str
class Color:
def __init__(self, r, g, b):
self.r, self.g, self.b = r, g, b
def __repr__(self):
return f"Color(r={self.r}, g={self.g}, b={self.b})"
# 没有 __str__
c = Color(255, 128, 0)
print(c) # Color(r=255, g=128, b=0) ← 没有 __str__,退回用 __repr__
print(repr(c)) # Color(r=255, g=128, b=0)
3.3 format——支持 format() 和 f-string 格式规范
class Money:
def __init__(self, amount, currency="CNY"):
self.amount = amount
self.currency = currency
def __str__(self):
return f"{self.amount} {self.currency}"
def __format__(self, format_spec):
"""
format_spec:格式说明符(f-string 冒号后面的部分)
例如:f"{money:.2f}" 中的 ".2f"
"""
if format_spec == "":
return str(self)
if format_spec == "symbol":
symbols = {"CNY": "¥", "USD": "$", "EUR": "€"}
sym = symbols.get(self.currency, self.currency)
return f"{sym}{self.amount:.2f}"
if format_spec == "full":
return f"{self.amount:.2f} {self.currency}(人民币)"
# 委托给默认浮点格式
return format(self.amount, format_spec)
price = Money(1999.5, "CNY")
print(f"{price}") # 1999.5 CNY (默认)
print(f"{price:symbol}") # ¥1999.50 (自定义格式)
print(f"{price:full}") # 1999.50 CNY(人民币)
print(f"{price:.0f}") # 2000 (委托浮点格式)
print(f"{price:,.2f}") # 1,999.50
第四部分:比较运算符
4.1 六个比较魔术方法
class Rectangle:
"""矩形类——按面积比较大小"""
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
def __repr__(self):
return f"Rectangle({self.width}×{self.height}, area={self.area})"
# == 和 !=
def __eq__(self, other):
if not isinstance(other, Rectangle):
return NotImplemented # 不知道如何比,返回 NotImplemented
return self.area == other.area
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return result
return not result
# < 和 >=
def __lt__(self, other):
if not isinstance(other, Rectangle):
return NotImplemented
return self.area < other.area
def __le__(self, other):
return self == other or self < other
# > 和 <=
def __gt__(self, other):
if not isinstance(other, Rectangle):
return NotImplemented
return self.area > other.area
def __ge__(self, other):
return self == other or self > other
r1 = Rectangle(3, 4) # 面积 12
r2 = Rectangle(2, 6) # 面积 12
r3 = Rectangle(5, 5) # 面积 25
print(r1 == r2) # True(面积相同)
print(r1 != r3) # True
print(r1 < r3) # True(12 < 25)
print(r3 > r2) # True
print(r1 >= r2) # True(面积相等)
# 可以使用 sorted、min、max!
rects = [r3, r1, r2]
print(sorted(rects)) # 按面积升序
print(min(rects)) # 最小面积
print(max(rects)) # 最大面积
4.2 functools.total_ordering——偷懒写法
只要定义 __eq__ 和一个比较方法(__lt__、__le__、__gt__、__ge__ 之一),@total_ordering 自动推导出其余的:
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __eq__(self, other):
return self.grade == other.grade
def __lt__(self, other):
return self.grade < other.grade
# 不用写 __le__、__gt__、__ge__,@total_ordering 自动生成!
students = [
Student("张三", 85),
Student("李四", 92),
Student("王五", 78),
]
print(sorted(students, key=lambda s: s.grade)) # 正常排序
print(max(students).name) # 李四(最高分)
print(min(students).grade) # 78(最低分)
s1 = Student("A", 85)
s2 = Student("B", 85)
print(s1 == s2) # True
print(s1 >= s2) # True(自动推导)
print(s1 <= s2) # True(自动推导)
第五部分:算术运算符
5.1 基本算术运算符
class Vector:
"""二维向量,支持所有常用运算"""
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# 加法:v1 + v2
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
# 减法:v1 - v2
def __sub__(self, other):
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
# 乘法:v * scalar(向量乘以标量)
def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
# 除法:v / scalar
def __truediv__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x / scalar, self.y / scalar)
return NotImplemented
# 取负:-v
def __neg__(self):
return Vector(-self.x, -self.y)
# 取正:+v
def __pos__(self):
return Vector(self.x, self.y)
# 绝对值(向量模):abs(v)
def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v2 - v1) # Vector(2, 2)
print(v1 * 3) # Vector(3, 6)
print(v2 / 2) # Vector(1.5, 2.0)
print(-v1) # Vector(-1, -2)
print(abs(v2)) # 5.0(√(9+16)=5)
5.2 反射运算符——当左边不支持时
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __mul__(self, scalar):
"""v * 3"""
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""
3 * v ← 当 int 不知道如何乘以 Vector 时,
Python 会尝试调用 Vector 的 __rmul__
r = reflected(反射/交换)
"""
return self.__mul__(scalar) # 乘法满足交换律,直接复用
v = Vector(1, 2)
print(v * 3) # Vector(3, 6) → 调用 v.__mul__(3)
print(3 * v) # Vector(3, 6) → int.__mul__(v) 失败 → 调用 v.__rmul__(3)
反射方法一览:
| 正常 | 反射 | 触发时机 |
|---|---|---|
__add__ |
__radd__ |
其他 + 自己 |
__sub__ |
__rsub__ |
其他 - 自己 |
__mul__ |
__rmul__ |
其他 * 自己 |
__truediv__ |
__rtruediv__ |
其他 / 自己 |
__pow__ |
__rpow__ |
其他 ** 自己 |
5.3 增量赋值运算符
class Counter:
def __init__(self, value=0):
self.value = value
def __repr__(self):
return f"Counter({self.value})"
def __add__(self, other):
if isinstance(other, int):
return Counter(self.value + other)
return NotImplemented
def __iadd__(self, other):
"""
+= 运算符(in-place add)
如果没有定义 __iadd__,Python 会退回用 __add__(创建新对象)
定义 __iadd__ 后,可以就地修改,避免创建新对象(更高效)
"""
if isinstance(other, int):
self.value += other # 就地修改!
return self # 必须返回 self
return NotImplemented
def __isub__(self, other):
"""-= 运算符"""
if isinstance(other, int):
self.value -= other
return self
return NotImplemented
c = Counter(10)
print(id(c)) # 某个内存地址,比如 140123456
c += 5 # 调用 __iadd__,就地修改
print(c) # Counter(15)
print(id(c)) # 同一个内存地址!(对象没有被重建)
c -= 3
print(c) # Counter(12)
5.4 其他算术运算符完整列表
class Num:
def __init__(self, v):
self.v = v
# 整数除法://
def __floordiv__(self, other): ...
# 取余:%
def __mod__(self, other): ...
# 同时返回商和余数(divmod(a, b))
def __divmod__(self, other): ...
# 幂:**
def __pow__(self, other, modulo=None): ...
# 位运算
def __and__(self, other): ... # &
def __or__(self, other): ... # |
def __xor__(self, other): ... # ^
def __lshift__(self, other): ... # <<
def __rshift__(self, other): ... # >>
def __invert__(self): ... # ~(按位取反,一元运算符)
第六部分:容器协议
6.1 len——len() 函数
class Playlist:
def __init__(self, name):
self.name = name
self._songs = []
def add(self, song):
self._songs.append(song)
def __len__(self):
"""支持 len(playlist)"""
return len(self._songs)
def __bool__(self):
"""支持 if playlist:(非空则为 True)"""
return len(self) > 0
p = Playlist("我的歌单")
print(len(p)) # 0
print(bool(p)) # False(空列表)
p.add("晴天")
p.add("夜曲")
print(len(p)) # 2
print(bool(p)) # True
if p:
print(f"播放列表 '{p.name}' 有 {len(p)} 首歌")
6.2 getitem、setitem、delitem——下标访问
class Matrix:
"""2D 矩阵,支持 m[i][j] 和 m[i, j] 两种访问方式"""
def __init__(self, rows, cols, fill=0):
self._data = [[fill] * cols for _ in range(rows)]
self.rows = rows
self.cols = cols
def __getitem__(self, key):
"""
支持两种访问:
m[i] → 返回第 i 行(列表)
m[i, j] → 返回第 i 行第 j 列的元素
"""
if isinstance(key, tuple):
i, j = key
return self._data[i][j]
return self._data[key]
def __setitem__(self, key, value):
"""
m[i, j] = value → 设置元素
m[i] = [1,2,3] → 设置整行
"""
if isinstance(key, tuple):
i, j = key
self._data[i][j] = value
else:
self._data[key] = value
def __repr__(self):
rows_str = "n ".join(str(row) for row in self._data)
return f"Matrix(n {rows_str}n)"
m = Matrix(3, 3)
m[0, 0] = 1
m[1, 1] = 5
m[2, 2] = 9
print(m[0, 0]) # 1
print(m[1]) # [0, 5, 0](整行)
print(m[1, 1]) # 5
print(m)
输出:
1
[0, 5, 0]
5
Matrix(
[1, 0, 0]
[0, 5, 0]
[0, 0, 9]
)
6.3 contains——in 运算符
class WordSet:
"""大小写不敏感的单词集合"""
def __init__(self, words):
self._words = {w.lower() for w in words}
def __contains__(self, word):
"""支持 word in wordset"""
return word.lower() in self._words
def __len__(self):
return len(self._words)
vocab = WordSet(["Python", "Java", "JavaScript", "C++"])
print("python" in vocab) # True(不区分大小写)
print("JAVA" in vocab) # True
print("Ruby" in vocab) # False
print("PHP" not in vocab) # True
print(len(vocab)) # 4
6.4 iter 和 next——让对象可迭代
class CountDown:
"""倒计时迭代器"""
def __init__(self, start):
self.start = start
self.current = start
def __iter__(self):
"""
返回迭代器对象本身。
如果对象本身就是迭代器,返回 self 即可。
"""
self.current = self.start # 重置(允许重复迭代)
return self
def __next__(self):
"""
每次迭代调用,返回下一个值。
当没有值时,抛出 StopIteration 通知循环结束。
"""
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
cd = CountDown(5)
# for 循环:自动调用 __iter__ 和 __next__
for n in cd:
print(n, end=" ") # 5 4 3 2 1
print()
# 可以重复迭代(因为 __iter__ 重置了 current)
for n in cd:
print(n, end=" ") # 5 4 3 2 1
print()
# 手动迭代
it = iter(cd) # 调用 cd.__iter__()
print(next(it)) # 5(调用 __next__)
print(next(it)) # 4
6.5 更简洁:只定义 iter 用 yield(生成器)
class EvenNumbers:
"""生成指定范围内所有偶数"""
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self):
"""用 yield 使 __iter__ 变成生成器,更简洁"""
n = self.start
while n < self.stop:
if n % 2 == 0:
yield n
n += 1
evens = EvenNumbers(1, 20)
for e in evens:
print(e, end=" ") # 2 4 6 8 10 12 14 16 18
print()
# 可以多次迭代(每次 __iter__ 产生新的生成器)
print(list(evens)) # [2, 4, 6, 8, 10, 12, 14, 16, 18]
print(sum(evens)) # 90
6.6 reversed——支持 reversed()
class SortedList:
def __init__(self, items):
self._data = sorted(items)
def __iter__(self):
return iter(self._data)
def __reversed__(self):
"""支持 reversed(obj)"""
return iter(reversed(self._data))
def __len__(self):
return len(self._data)
sl = SortedList([3, 1, 4, 1, 5, 9, 2, 6])
print(list(sl)) # [1, 1, 2, 3, 4, 5, 6, 9]
print(list(reversed(sl))) # [9, 6, 5, 4, 3, 2, 1, 1]
第七部分:可调用对象
7.1 call——让对象像函数一样调用
class Multiplier:
"""创建一个"乘以 n"的可调用对象"""
def __init__(self, n):
self.n = n
def __call__(self, x):
"""obj(x) 时被调用"""
return x * self.n
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10(像函数一样调用!)
print(triple(5)) # 15
print(double(3.14)) # 6.28
# 验证:callable() 检查对象是否可调用
print(callable(double)) # True
print(callable(42)) # False
7.2 call 实战:带状态的函数(计数器)
class CallCounter:
"""记录函数被调用次数的装饰器类"""
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"[第{self.count}次调用] {self.func.__name__}")
return self.func(*args, **kwargs)
def reset(self):
self.count = 0
@CallCounter
def greet(name):
return f"你好,{name}!"
print(greet("张三")) # 第1次调用
print(greet("李四")) # 第2次调用
print(greet("王五")) # 第3次调用
print(f"共调用了 {greet.count} 次")
输出:
[第1次调用] greet
你好,张三!
[第2次调用] greet
你好,李四!
[第3次调用] greet
你好,王五!
共调用了 3 次
7.3 call 实战:带参数记忆的累加器
class Accumulator:
"""每次调用都在上次结果基础上累加"""
def __init__(self, initial=0):
self.total = initial
def __call__(self, value):
self.total += value
return self.total
def reset(self):
self.total = 0
return self
acc = Accumulator()
print(acc(10)) # 10
print(acc(20)) # 30
print(acc(5)) # 35
print(acc(-8)) # 27
acc.reset()
print(acc(100)) # 100
第八部分:属性访问控制
8.1 getattr——访问不存在的属性时调用
class FlexibleConfig:
"""
灵活的配置类:
- 访问已设置的属性:正常返回
- 访问未设置的属性:返回 None(而不是报 AttributeError)
"""
def __init__(self, **kwargs):
self._data = dict(kwargs)
def __getattr__(self, name):
"""
只有当普通属性查找失败后,才会调用 __getattr__
(注意:不是每次属性访问都调用,只有找不到时才调用)
"""
if name.startswith("_"):
raise AttributeError(name) # 私有属性找不到就报错
return self._data.get(name, None) # 其他属性返回 None
def __setattr__(self, name, value):
if name.startswith("_"):
super().__setattr__(name, value) # 私有属性正常设置
else:
self._data[name] = value
cfg = FlexibleConfig(debug=True, host="localhost")
print(cfg.debug) # True(已设置)
print(cfg.host) # localhost(已设置)
print(cfg.port) # None(未设置,不报错)
print(cfg.timeout) # None(未设置,不报错)
cfg.port = 8080
print(cfg.port) # 8080
8.2 setattr 和 delattr
class Validated:
"""属性赋值时自动验证类型"""
_schema = {
"name": str,
"age": int,
"score": float,
}
def __setattr__(self, name, value):
"""每次给属性赋值时都会调用"""
if name in self._schema:
expected_type = self._schema[name]
if not isinstance(value, expected_type):
raise TypeError(
f"'{name}' 必须是 {expected_type.__name__},"
f"不能是 {type(value).__name__}"
)
# 调用父类的 __setattr__ 实际执行赋值
# 不能写 self.name = value(会无限递归!)
super().__setattr__(name, value)
def __delattr__(self, name):
"""del obj.attr 时调用"""
if name in self._schema:
raise AttributeError(f"不允许删除 '{name}' 属性")
super().__delattr__(name)
v = Validated()
v.name = "张三" # ✅ str
v.age = 25 # ✅ int
v.score = 95.5 # ✅ float
print(v.name, v.age, v.score)
try:
v.age = "二十五" # ❌ 应该是 int,传了 str
except TypeError as e:
print(f"类型错误:{e}")
try:
del v.name # ❌ 不允许删除
except AttributeError as e:
print(f"删除失败:{e}")
8.3 getattribute——拦截所有属性访问
class LoggedAccess:
"""每次访问属性时都打印日志"""
def __init__(self, x, y):
# 注意:这里要用 super().__setattr__,否则会无限递归
super().__setattr__("x", x)
super().__setattr__("y", y)
super().__setattr__("_access_count", 0)
def __getattribute__(self, name):
"""
每次访问任何属性时都调用(包括 self.x、self.method 等)
注意:必须用 super().__getattribute__() 获取实际值,
否则会无限递归(因为 self.xxx 本身就会调用 __getattribute__)
"""
if not name.startswith("_"):
count = super().__getattribute__("_access_count")
super().__setattr__("_access_count", count + 1)
print(f"[访问] 属性 '{name}'(第{count+1}次)")
return super().__getattribute__(name)
obj = LoggedAccess(3, 4)
print(obj.x) # [访问] 属性 'x'(第1次)→ 3
print(obj.y) # [访问] 属性 'y'(第2次)→ 4
print(obj.x) # [访问] 属性 'x'(第3次)→ 3
注意:
__getattr__和__getattribute__的区别:
__getattr__:只在找不到属性时调用(兜底)__getattribute__:每次属性访问都调用(拦截全部)
第九部分:上下文管理器
9.1 enter 和 exit
with 语句背后的魔术方法(详细原理见第1章,这里重点讲实战):
class Timer:
"""计时器上下文管理器"""
import time as _time
def __enter__(self):
"""进入 with 块时调用"""
self._start = Timer._time.perf_counter()
print("计时开始...")
return self # 返回值赋给 as 后的变量
def __exit__(self, exc_type, exc_val, exc_tb):
"""
退出 with 块时调用(无论是否异常)
exc_type/exc_val/exc_tb:异常信息(无异常则都是 None)
返回 True:压制异常;返回 False/None:不压制
"""
elapsed = Timer._time.perf_counter() - self._start
print(f"耗时:{elapsed:.4f} 秒")
if exc_type is not None:
print(f"期间发生了异常:{exc_type.__name__}: {exc_val}")
return False # 不压制,让异常继续传播
return False
# 正常使用
with Timer() as t:
total = sum(range(1_000_000))
print(f"计算结果:{total}")
print()
# 有异常的情况
try:
with Timer():
x = 1 / 0 # 触发 ZeroDivisionError
except ZeroDivisionError:
print("外层捕获了异常")
输出:
计时开始...
计算结果:499999500000
耗时:0.0312 秒
计时开始...
耗时:0.0001 秒
期间发生了异常:ZeroDivisionError: division by zero
外层捕获了异常
9.2 实战:数据库事务上下文管理器
class FakeDB:
"""模拟数据库操作"""
def __init__(self):
self._data = {"balance": 1000}
self._backup = {}
self.connected = False
def connect(self):
self.connected = True
print("[DB] 连接建立")
def disconnect(self):
self.connected = False
print("[DB] 连接关闭")
def get(self, key):
return self._data.get(key)
def set(self, key, value):
self._data[key] = value
def __enter__(self):
"""进入事务:连接并备份数据"""
self.connect()
self._backup = self._data.copy() # 备份
print(f"[DB] 事务开始,当前余额:{self._data['balance']}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出事务:成功提交,失败回滚"""
if exc_type is None:
print(f"[DB] 事务提交,新余额:{self._data['balance']}")
else:
self._data = self._backup # 回滚!
print(f"[DB] 事务回滚,余额恢复:{self._data['balance']}")
self.disconnect()
return False # 不压制异常
db = FakeDB()
print("=== 成功事务 ===")
with db as conn:
conn.set("balance", conn.get("balance") - 200)
print(f" 扣款后余额:{conn.get('balance')}")
print()
print("=== 失败事务(自动回滚)===")
try:
with db as conn:
conn.set("balance", conn.get("balance") - 300)
raise RuntimeError("支付网关超时!")
except RuntimeError as e:
print(f" 业务层捕获:{e}")
输出:
=== 成功事务 ===
[DB] 连接建立
[DB] 事务开始,当前余额:1000
扣款后余额:800
[DB] 事务提交,新余额:800
[DB] 连接关闭
=== 失败事务(自动回滚)===
[DB] 连接建立
[DB] 事务开始,当前余额:800
[DB] 事务回滚,余额恢复:800
[DB] 连接关闭
业务层捕获:支付网关超时!
第十部分:布尔与哈希
10.1 bool——对象的布尔值
class BankAccount:
def __init__(self, balance):
self.balance = balance
def __bool__(self):
"""
if account: 时调用
如果没有定义 __bool__,Python 会尝试 len(),再无则视为 True
"""
return self.balance > 0 # 有余额才为 True
def __repr__(self):
return f"BankAccount(balance={self.balance})"
a1 = BankAccount(1000)
a2 = BankAccount(0)
a3 = BankAccount(-50)
print(bool(a1)) # True
print(bool(a2)) # False(余额为0)
print(bool(a3)) # False(余额为负)
for acc in [a1, a2, a3]:
if acc:
print(f"{acc} → 正常账户")
else:
print(f"{acc} → 账户余额不足")
10.2 hash——对象可以放进集合/字典
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
def __hash__(self):
"""
定义 __eq__ 后,必须也定义 __hash__,
否则对象不能放入 set 或作为 dict 的键。
规则:相等的对象必须有相同的 hash 值
"""
return hash((self.x, self.y)) # 用元组的 hash
def __repr__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)
print(p1 == p2) # True(相等)
print(hash(p1) == hash(p2)) # True(hash 也相等)
# 可以放进 set
points_set = {p1, p2, p3}
print(points_set) # {Point(1, 2), Point(3, 4)}(p1 和 p2 相等,只保留一个)
print(len(points_set)) # 2
# 可以作为 dict 的键
locations = {p1: "起点", p3: "终点"}
print(locations[Point(1, 2)]) # 起点(用相等的新对象查找)
注意事项:
⚠️ 定义规则:
- 如果定义了 __eq__,默认 __hash__ 被设置为 None(对象不可哈希)
- 要让对象可哈希,必须同时定义 __hash__
- 如果对象是可变的(mutable),不应该定义 __hash__(因为修改后 hash 会变,导致在集合/字典里找不到它)
第十一部分:类型转换
11.1 类型转换魔术方法
class Fraction:
"""分数类,支持转换为 int、float、str 等类型"""
def __init__(self, numerator, denominator=1):
from math import gcd
g = gcd(abs(numerator), abs(denominator))
self.num = numerator // g # 分子
self.den = denominator // g # 分母
def __repr__(self):
return f"Fraction({self.num}, {self.den})"
def __str__(self):
if self.den == 1:
return str(self.num)
return f"{self.num}/{self.den}"
def __int__(self):
"""int(fraction)"""
return self.num // self.den
def __float__(self):
"""float(fraction)"""
return self.num / self.den
def __bool__(self):
"""if fraction:"""
return self.num != 0
def __bytes__(self):
"""bytes(fraction)"""
return str(self).encode("utf-8")
def __complex__(self):
"""complex(fraction)"""
return complex(float(self), 0)
def __round__(self, n=0):
"""round(fraction, n)"""
return round(float(self), n)
f = Fraction(7, 4)
print(str(f)) # 7/4
print(int(f)) # 1(截断,不是四舍五入)
print(float(f)) # 1.75
print(bool(f)) # True
print(bytes(f)) # b'7/4'
print(complex(f)) # (1.75+0j)
print(round(f, 1)) # 1.8
print(round(f)) # 2
zero = Fraction(0, 5)
print(bool(zero)) # False
第十二部分:内存优化
12.1 slots——减少内存占用
默认情况下,Python 对象把属性存储在 __dict__(字典)里,字典本身有额外开销。__slots__ 用固定的数组代替字典,节省内存:
import sys
# 普通类(使用 __dict__)
class NormalPoint:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
# 使用 __slots__ 的类
class SlottedPoint:
__slots__ = ["x", "y", "z"] # 只允许这几个属性
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
p1 = NormalPoint(1, 2, 3)
p2 = SlottedPoint(1, 2, 3)
print(f"普通对象内存:{sys.getsizeof(p1)} bytes + dict {sys.getsizeof(p1.__dict__)} bytes")
print(f"Slots 对象内存:{sys.getsizeof(p2)} bytes(无 __dict__)")
# 普通类可以动态添加属性
p1.w = 4 # ✅ 可以
print(p1.w)
# Slots 类不能动态添加属性
try:
p2.w = 4 # ❌ 报错
except AttributeError as e:
print(f"错误:{e}")
# ─── 内存对比实验 ───
N = 100_000
normal_points = [NormalPoint(i, i, i) for i in range(N)]
slotted_points = [SlottedPoint(i, i, i) for i in range(N)]
import tracemalloc
tracemalloc.start()
n_pts = [NormalPoint(i, i, i) for i in range(N)]
n_mem = tracemalloc.get_traced_memory()[1]
tracemalloc.stop()
tracemalloc.start()
s_pts = [SlottedPoint(i, i, i) for i in range(N)]
s_mem = tracemalloc.get_traced_memory()[1]
tracemalloc.stop()
print(f"n{N} 个对象:")
print(f" 普通类:{n_mem / 1024 / 1024:.1f} MB")
print(f" Slots :{s_mem / 1024 / 1024:.1f} MB")
print(f" 节省:{(1 - s_mem/n_mem)*100:.0f}%")
第十三部分:综合实战——完整的自定义容器类
13.1 实现一个功能完整的”有序字典集合”
class SortedDict:
"""
按值排序的字典,支持完整的 Python 容器协议:
- 下标访问和赋值
- in 运算符
- len()、iter()、reversed()
- str/repr 输出
- 比较运算
"""
def __init__(self, **kwargs):
self._data = dict(kwargs)
# ─── 字符串表示 ───
def __repr__(self):
sorted_items = sorted(self._data.items(), key=lambda kv: kv[1])
items_str = ", ".join(f"{k!r}: {v!r}" for k, v in sorted_items)
return f"SortedDict({{{items_str}}})"
def __str__(self):
sorted_items = sorted(self._data.items(), key=lambda kv: kv[1])
return "{" + ", ".join(f"{k}: {v}" for k, v in sorted_items) + "}"
# ─── 容器协议 ───
def __len__(self):
return len(self._data)
def __contains__(self, key):
return key in 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 __iter__(self):
"""按值从小到大迭代键"""
return iter(sorted(self._data, key=self._data.get))
def __reversed__(self):
"""按值从大到小迭代键"""
return iter(sorted(self._data, key=self._data.get, reverse=True))
# ─── 算术运算 ───
def __add__(self, other):
"""合并两个 SortedDict(后者覆盖前者)"""
if isinstance(other, SortedDict):
new = SortedDict(**self._data)
new._data.update(other._data)
return new
return NotImplemented
# ─── 比较 ───
def __eq__(self, other):
if isinstance(other, SortedDict):
return self._data == other._data
return NotImplemented
# ─── 布尔 ───
def __bool__(self):
return bool(self._data)
# ─── 可调用:作为函数使用,批量更新 ───
def __call__(self, **kwargs):
self._data.update(kwargs)
return self
# ─── 使用示例 ───
scores = SortedDict(张三=85, 李四=92, 王五=78, 赵六=95)
print(f"有 {len(scores)} 个学生")
print(f"张三的成绩:{scores['张三']}")
print(f"'李四' in scores:{'李四' in scores}")
print("n按成绩升序:")
for name in scores:
print(f" {name}: {scores[name]}")
print("n按成绩降序:")
for name in reversed(scores):
print(f" {name}: {scores[name]}")
# 添加学生
scores["钱七"] = 88
print(f"n添加钱七后:{scores}")
# 合并
extra = SortedDict(孙八=91, 周九=76)
all_scores = scores + extra
print(f"n合并后:{all_scores}")
# 用 __call__ 批量更新
scores(张三=90, 李四=95)
print(f"n更新后张三:{scores['张三']},李四:{scores['李四']}")
输出:
有 4 个学生
张三的成绩:85
'李四' in scores:True
按成绩升序:
王五: 78
张三: 85
李四: 92
赵六: 95
按成绩降序:
赵六: 95
李四: 92
张三: 85
王五: 78
添加钱七后:{王五: 78, 张三: 85, 钱七: 88, 李四: 92, 赵六: 95}
合并后:{周九: 76, 王五: 78, 张三: 85, 钱七: 88, 孙八: 91, 李四: 92, 赵六: 95}
更新后张三:90,李四:95
第十四部分:常见陷阱与注意事项
14.1 setattr 里的无限递归
class Bad:
def __setattr__(self, name, value):
self.name = value # ❌ 无限递归!self.name 又触发 __setattr__
class Good:
def __setattr__(self, name, value):
# ✅ 必须调用父类的 __setattr__ 来实际赋值
super().__setattr__(name, value)
# 或者:object.__setattr__(self, name, value)
14.2 定义 eq 后忘记定义 hash
class Broken:
def __init__(self, x):
self.x = x
def __eq__(self, other):
return self.x == other.x
# 没有定义 __hash__!
b = Broken(1)
try:
s = {b} # ❌ TypeError: unhashable type: 'Broken'
except TypeError as e:
print(f"错误:{e}")
# ✅ 修复:同时定义 __hash__
class Fixed:
def __init__(self, x):
self.x = x
def __eq__(self, other):
return isinstance(other, Fixed) and self.x == other.x
def __hash__(self):
return hash(self.x)
f = Fixed(1)
s = {f} # ✅ 正常
print(s)
14.3 str 里的无限递归
class Recursive:
def __str__(self):
return f"值是:{self}" # ❌ f-string 里的 self 又调用 __str__,无限递归!
class Safe:
def __init__(self, value):
self.value = value
def __str__(self):
return f"值是:{self.value}" # ✅ 用 self.value,不是 self
14.4 不要直接调用 dunder 方法
# ❌ 不推荐:直接调用魔术方法
a = [1, 2, 3]
n = a.__len__() # 不推荐
item = a.__getitem__(0) # 不推荐
# ✅ 推荐:用对应的内置函数/运算符
n = len(a) # 推荐
item = a[0] # 推荐
# 原因:内置函数有额外的优化和类型检查
14.5 NotImplemented vs NotImplementedError
# NotImplemented(值):用于双目运算符,告诉 Python"我不知道怎么处理,试试另一边"
class A:
def __add__(self, other):
if not isinstance(other, A):
return NotImplemented # ← 值,不是异常
return A()
# NotImplementedError(异常):用于抽象方法,表示子类必须实现
class Animal:
def speak(self):
raise NotImplementedError("子类必须实现 speak() 方法") # ← 异常
class Dog(Animal):
def speak(self):
return "汪汪!"
第十五部分:小结速查表
15.1 魔术方法总览
生命周期:
__new__(cls, ...) 创建实例(返回实例)
__init__(self, ...) 初始化实例
__del__(self) 对象被销毁时
字符串表示:
__str__(self) str(obj)、print(obj)
__repr__(self) repr(obj)、调试显示
__format__(self, spec) f"{obj:spec}"、format(obj, spec)
比较运算符:
__eq__(self, other) == __ne__(self, other) !=
__lt__(self, other) < __gt__(self, other) >
__le__(self, other) <= __ge__(self, other) >=
算术运算符:
__add__ + __radd__ 反射加法
__sub__ - __rsub__ 反射减法
__mul__ * __rmul__ 反射乘法
__truediv__ / __rtruediv__ 反射除法
__floordiv__ // __mod__ %
__pow__ ** __neg__ -x(取负)
__abs__ abs() __pos__ +x(取正)
增量赋值:
__iadd__ += __isub__ -= __imul__ *=
__itruediv__ /= __ifloordiv__ //=
容器协议:
__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
__iter__(self) for x in obj
__next__(self) next(obj)
__reversed__(self) reversed(obj)
可调用:
__call__(self, ...) obj(...)
属性访问:
__getattr__(self, name) 只在找不到属性时调用
__setattr__(self, name, value) 每次赋值时调用
__delattr__(self, name) del obj.attr
__getattribute__(self, name) 每次访问属性时调用
上下文管理器:
__enter__(self) with obj as x:(进入)
__exit__(self, exc_t, exc_v, tb) with 块退出时
布尔与哈希:
__bool__(self) bool(obj)、if obj:
__hash__(self) hash(obj)、用于 set/dict
类型转换:
__int__ int(obj)
__float__ float(obj)
__complex__ complex(obj)
__bytes__ bytes(obj)
__round__ round(obj, n)
内存优化:
__slots__ = [...] 替换 __dict__,节省内存
15.2 最常用魔术方法一览
| 优先级 | 方法 | 说明 |
|---|---|---|
| ⭐⭐⭐ | __init__ |
几乎每个类都要 |
| ⭐⭐⭐ | __repr__ |
调试必备,建议每个类都写 |
| ⭐⭐⭐ | __str__ |
友好输出 |
| ⭐⭐⭐ | __eq__ |
定义相等性 |
| ⭐⭐⭐ | __len__ + __getitem__ + __iter__ |
容器类必备 |
| ⭐⭐ | __add__ 等运算符 |
数值/向量类 |
| ⭐⭐ | __enter__ + __exit__ |
资源管理 |
| ⭐⭐ | __hash__ |
定义了 __eq__ 就要定义 |
| ⭐⭐ | __bool__ |
控制对象的真假值 |
| ⭐ | __call__ |
可调用对象 |
| ⭐ | __getattr__ |
属性访问控制 |
| ⭐ | __slots__ |
性能优化 |