23.Python 魔术方法完全指南

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 strrepr 的区别

这是新手最常困惑的区别之一:

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 getitemsetitemdelitem——下标访问

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 iternext——让对象可迭代

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 setattrdelattr

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 enterexit

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

发表评论