第1章:Python数据模型
一、导读
1.1 本章学习目标
第1章讲解 Python 的数据模型——魔术方法(dunder methods)如何让自定义对象与 Python 语言的核心特性(迭代、索引、上下文管理等)无缝集成。
本章核心学习目标:
- 理解 Python 数据模型:魔术方法如何驱动语言特性
- 掌握核心协议:
__repr__、__iter__、__getitem__、__len__ - 理解协议(Protocol)的概念:不需要继承,只需实现特定方法
- 学会用魔术方法让对象表现得像内置类型
1.2 为什么本章如此重要
Python 数据模型是理解 Python 语言行为的基础。掌握魔术方法,能写出"Pythonic"的代码——让自定义对象像内置类型一样自然。
1.3 核心问题引导
for x in obj 背后发生了什么?
len(obj) 和 obj.__len__() 有什么区别?
如何实现一个支持切片和迭代的自定义容器?
二、核心概念详解
2.1 魔术方法与协议
python
class Card:
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
def __repr__(self):
return f'Card({self.suit}, {self.rank})'
def __str__(self):
return f'{self.rank} of {self.suit}'
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(suit, rank)
for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
deck = FrenchDeck()
print(len(deck)) # 52, 调用 __len__
print(deck[0]) # Card(spades, 2), 调用 __getitem__
print(deck[-1]) # Card(hearts, A)
print(deck[12::13]) # 切片也工作
# 迭代: 没有 __iter__,Python 用 __getitem__ 实现
for card in deck:
print(card)
# in 操作符
print(Card('hearts', 'A') in deck) # True
# 随机选择
from random import choice
print(choice(deck))2.2 迭代协议
python
class Sentence:
def __init__(self, text):
self.text = text
self.words = text.split()
def __repr__(self):
return f'Sentence({self.text!r})'
def __iter__(self):
return iter(self.words)
def __len__(self):
return len(self.words)
s = Sentence("Hello World Python")
for word in s:
print(word)
# 迭代协议:
# 1. 调用 __iter__() 获取迭代器
# 2. 反复调用 __next__() 直到 StopIteration
# 3. for/in/sum/min/max 等都使用这个协议2.3 上下文管理器协议
python
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.elapsed = time.perf_counter() - self.start
print(f"Elapsed: {self.elapsed:.4f}s")
return False # 不抑制异常
with Timer():
sum(range(10_000_000))
# 输出: Elapsed: 0.1234s三、实战案例
3.1 案例一:实现一个向量类
python
import math
from typing import Iterator
class Vector:
def __init__(self, *components):
self._components = tuple(components)
def __repr__(self):
return f'Vector{self._components}'
def __len__(self):
return len(self._components)
def __getitem__(self, index):
return self._components[index]
def __iter__(self) -> Iterator[float]:
return iter(self._components)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __eq__(self, other):
if isinstance(other, Vector):
return self._components == other._components
return NotImplemented
def __add__(self, other):
if isinstance(other, Vector):
if len(self) != len(other):
raise ValueError("Vectors must have same length")
return Vector(*(a + b for a, b in zip(self, other)))
return NotImplemented
def __mul__(self, scalar):
return Vector(*(x * scalar for x in self))
def __rmul__(self, scalar):
return self.__mul__(scalar)
def dot(self, other):
return sum(a * b for a, b in zip(self, other))
v1 = Vector(1, 2, 3)
v2 = Vector(4, 5, 6)
print(abs(v1)) # 3.7416...
print(v1 + v2) # Vector(5, 7, 9)
print(v1 * 3) # Vector(3, 6, 9)
print(3 * v1) # Vector(3, 6, 9)
print(v1.dot(v2)) # 32
print(list(v1)) # [1, 2, 3]四、本章小结
Python 数据模型通过魔术方法让自定义对象与语言核心特性集成
迭代协议:__iter__ + __next__ 驱动 for/in 等语法
上下文管理器:__enter__ + __exit__ 驱动 with 语句
五、思考与练习
__repr__ 和 __str__ 的区别是什么?
如何实现一个支持负索引的自定义序列?