第1章 Python数据模型
按《Fluent Python, 2nd Edition》第1章重建:Python数据模型、特殊方法、Python风格纸牌、数值类型、字符串表示与布尔值、为何len不是方法。
从一个错误直觉开始
先预测:只记住Python数据模型的语法,是否足以写出可靠实现?不够。真正需要掌握的是对象、协议、状态和失败路径怎样协作。不是孤立技巧,而是把调用方意图映射到可测试行为的模型。
核心模型与责任
Python数据模型是一组由解释器调用的协议。len、迭代、切片、运算符与上下文管理看似是不同语法,实际都落到对象的特殊方法;应用代码使用公开语法,类型作者才实现双下划线方法。 这一章的阅读方法是先写出输入和不变量,再观察解释器或标准库采取哪条路径,最后用反例确认边界。这样学习得到的是可迁移的判断,而不是只能复述示例。
机制一:第一条执行链
Python风格纸牌只实现len与getitem就得到长度、索引、切片、迭代和随机抽取能力,因为解释器会沿序列协议提供回退行为。这个例子说明协议组合比堆叠普通方法更能融入语言。
from collections import namedtuple
Card = namedtuple('Card', 'rank 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(r, s) for s in self.suits for r in self.ranks]
def __len__(self): return len(self._cards)
def __getitem__(self, position): return self._cards[position]运行后记录对象身份、返回类型和异常位置;再改变一个输入,确认变化来自协议本身,而不是偶然的实现细节。
机制二:状态、类型与协作
数值类型通过repr、abs、bool、add与mul表达表示、模长、真值和运算。二元运算遇到不支持类型应返回NotImplemented,让解释器尝试反向方法,而不是提前封死协作机会。
class Vector:
def __init__(self, x, y): self.x, self.y = x, y
def __repr__(self): return f'Vector({self.x!r}, {self.y!r})'
def __abs__(self): return (self.x ** 2 + self.y ** 2) ** 0.5
def __bool__(self): return bool(abs(self))评审这段代码时,先问调用端依赖什么能力,再问实现是否扩大了共享状态或静默吞掉错误。接口越小,替换和测试通常越直接。
机制三:边界条件与成本
字符串表示要区分给开发者诊断的repr和给最终用户的str;布尔值优先调用bool,缺失时再依据len。每个入口都应维护清晰不变量,避免表示函数产生副作用。 为何len不是方法的答案同时涉及一致接口和实现效率:内置类型可以由CPython直接读取结构体长度,自定义类型则经len协议校验返回值。调用方无需知道对象来自Python还是C。
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)分步验证
代码实验与诊断
把三段代码保存为同一最小实验,先运行原始版本,再只改变一个维度。至少检查:公开结果是否稳定、失败是否在正确边界暴露、是否产生意外别名或资源泄漏、类型提示与运行时行为是否一致。观察变化后写出因果解释,而不是只记录输出。
常见误区
本章回顾
官方核心单元核对:Python数据模型;特殊方法;Python风格纸牌;数值类型、字符串表示与布尔值;为何len不是方法。这些单元共同回答Python数据模型如何从语言机制落到工程边界;下一步应能在没有范例答案时画出执行链、实现最小版本并解释反例。