欢迎光临
我们一直在努力

23.Python 魔术方法完全指南

文章目录

    • 第一部分:魔术方法是什么?
      • 1.1 什么是魔术方法
      • 1.2 不用魔术方法 vs 用魔术方法
    • 第二部分:对象的生命周期
      • 2.1 __init__——初始化对象(最常用)
      • 2.2 __new__——创建对象(比 __init__ 更底层)
      • 2.3 __del__——析构(对象被销毁时调用)
    • 第三部分:字符串表示
      • 3.1 __str__ 和 __repr__ 的区别
      • 3.2 只定义 __repr__ 不定义 __str__
      • 3.3 __format__——支持 format() 和 f-string 格式规范
    • 第四部分:比较运算符
      • 4.1 六个比较魔术方法
      • 4.2 functools.total_ordering——偷懒写法
    • 第五部分:算术运算符
      • 5.1 基本算术运算符
      • 5.2 反射运算符——当左边不支持时
      • 5.3 增量赋值运算符
      • 5.4 其他算术运算符完整列表
    • 第六部分:容器协议
      • 6.1 __len__——len() 函数
      • 6.2 __getitem__、__setitem__、__delitem__——下标访问
      • 6.3 __contains__——in 运算符
      • 6.4 __iter__ 和 __next__——让对象可迭代
      • 6.5 更简洁:只定义 __iter__ 用 yield(生成器)
      • 6.6 __reversed__——支持 reversed()
    • 第七部分:可调用对象
      • 7.1 __call__——让对象像函数一样调用
      • 7.2 __call__ 实战:带状态的函数(计数器)
      • 7.3 __call__ 实战:带参数记忆的累加器
    • 第八部分:属性访问控制
      • 8.1 __getattr__——访问不存在的属性时调用
      • 8.2 __setattr__ 和 __delattr__
      • 8.3 __getattribute__——拦截所有属性访问
    • 第九部分:上下文管理器
      • 9.1 __enter__ 和 __exit__
      • 9.2 实战:数据库事务上下文管理器
    • 第十部分:布尔与哈希
      • 10.1 __bool__——对象的布尔值
      • 10.2 __hash__——对象可以放进集合/字典
    • 第十一部分:类型转换
      • 11.1 类型转换魔术方法
    • 第十二部分:内存优化
      • 12.1 __slots__——减少内存占用
    • 第十三部分:综合实战——完整的自定义容器类
      • 13.1 实现一个功能完整的"有序字典集合"
    • 第十四部分:常见陷阱与注意事项
      • 14.1 __setattr__ 里的无限递归
      • 14.2 定义 __eq__ 后忘记定义 __hash__
      • 14.3 __str__ 里的无限递归
      • 14.4 不要直接调用 __dunder__ 方法
      • 14.5 NotImplemented vs NotImplementedError
    • 第十五部分:小结速查表
      • 15.1 魔术方法总览
      • 15.2 最常用魔术方法一览

23.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__ 性能优化

23.Python 魔术方法完全指南

赞(0)
未经允许不得转载:171主机测评 » 23.Python 魔术方法完全指南
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址