一. 什么是数据模型?
Python数据模型是Python对象系统的抽象,通过一组特殊方法(如__init__
、__len__
等)和协议(如迭代协议、上下文管理协议),定义了对象如何与语言的内置功能(如len()
、for
循环等)交互。
核心思想
- 统一性:所有对象(如列表、字典、自定义类)的行为都通过相同的特殊方法实现。
- 灵活性:通过实现特殊方法,可以让自定义对象支持内置操作(如
+
、in
、切片等)。
例子
import collections
Card = collections.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(rank, suit) 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]
解释
模块导入:
import collections
导入了 Python 标准库中的
collections
模块。命名元组定义:
Card
:
Card = collections.namedtuple('Card', ['rank', 'suit'])
使用
namedtuple
创建了一个名为Card
的简单类,表示扑克牌的一张牌。每个Card
对象有两个属性:
rank
:表示牌的点数(如'2'
,'J'
,'A'
等)。suit
:表示牌的花色(如'spades'
,'diamonds'
等)。类定义:
FrenchDeck
:
- 定义了一个名为
FrenchDeck
的类,表示一副标准的 52 张扑克牌。- 类属性:
ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split()
ranks
:表示牌的所有点数,包括数字牌('2'
到'10'
)和字母牌('J'
,'Q'
,'K'
,'A'
)。suits
:表示牌的所有花色('spades'
黑桃、'diamonds'
方片、'clubs'
梅花、'hearts'
红心)。- 构造函数
__init__
:初始化时创建了一副完整的扑克牌,存储在
def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
_cards
列表中。通过列表推导式生成所有可能的牌组合。- 方法
__len__
:实现了特殊方法
def __len__(self): return len(self._cards)
__len__
,使得可以通过len(deck)
获取扑克牌的数量。- 方法
__getitem__
:实现了特殊方法
def __getitem__(self, position): return self._cards[position]
__getitem__
,使得可以通过索引访问扑克牌,例如deck[0]
。
常用特殊方法:
方法 | 对应操作 |
---|---|
__init__ |
对象初始化 |
__repr__ / __str__ |
字符串表示 |
__len__ |
len(obj) |
__getitem__ |
obj[key] (支持索引和切片) |
__iter__ |
for x in obj (迭代) |
__enter__ / __exit__ |
with 语句(上下文管理) |
迭代协议
实现__iter__
和__next__
方法,让对象支持for
循环:
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
else:
self.current -= 1
return self.current + 1
for num in CountDown(3):
print(num) # 输出: 3 2 1
解释
类定义:
CountDown
:
定义了一个名为
CountDown
的类,用于实现倒计时功能。构造函数
__init__
:
def __init__(self, start): self.current = start
初始化时接收一个参数
start
,表示倒计时的起始值,并将其赋值给实例属性self.current
。方法
__iter__
:
def __iter__(self): return self
实现了可迭代协议,使得该类的实例可以作为迭代器使用。返回自身(
self
)。方法
__next__
:
def __next__(self): if self.current <= 0: raise StopIteration else: self.current -= 1 return self.current + 1
实现了迭代器协议中的
__next__
方法:
- 如果当前值
self.current
小于或等于 0,则抛出StopIteration
异常,表示迭代结束。- 否则,将
self.current
减 1,并返回减 1 前的值(即当前值)。使用场景:
- 该类可以通过
for
循环或手动调用next()
方法进行倒计时。- 示例:
输出结果:countdown = CountDown(5) for value in countdown: print(value)
5 4 3 2 1
上下文管理协议
实现__enter__
和__exit__
方法,支持with
语句:
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
print(f"耗时: {self.end - self.start:.2f}秒")
with Timer():
time.sleep(1) # 输出: 耗时: 1.00秒
解释
类定义:
Timer
:
定义了一个名为
Timer
的类,用于测量代码块的执行时间。方法
__enter__
:
def __enter__(self): self.start = time.time() return self
- 实现了上下文管理协议中的
__enter__
方法。- 在进入
with
语句块时,记录当前时间(以秒为单位)并存储在实例属性self.start
中。- 返回自身(
self
),以便在with
语句块中可以访问该对象。方法
__exit__
:
def __exit__(self, exc_type, exc_val, exc_tb): self.end = time.time() print(f"耗时: {self.end - self.start:.2f}秒")
- 实现了上下文管理协议中的
__exit__
方法。- 在退出
with
语句块时,记录当前时间(以秒为单位)并存储在实例属性self.end
中。- 计算执行时间(
self.end - self.start
),并以两位小数的格式打印到控制台。使用场景:
- 通过
with
语句使用Timer
类来测量代码块的执行时间。- 示例:
with Timer(): time.sleep(1) # 输出: 耗时: 1.00秒
- 在
with
语句块中调用了time.sleep(1)
,模拟了一个耗时 1 秒的操作。Timer
类会自动计算并打印出这段代码的执行时间。
让自定义类支持切片
class MySequence:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, index):
if isinstance(index, slice):
return self.data[index.start : index.stop : index.step]
else:
return self.data[index]
seq = MySequence([0, 1, 2, 3, 4])
print(seq[1:3]) # [1, 2](支持切片!)
鸭子类型(Duck Typing)
Python不检查对象的类型,而是检查对象是否实现了特定的方法(即是否“像鸭子一样叫”)。
示例:
class FakeList:
def __len__(self):
return 10
def __getitem__(self, index):
return index * 2
fake = FakeList()
print(len(fake)) # 10(调用__len__)
print(fake[5]) # 10(调用__getitem__)
print(isinstance(fake, list)) # False,但行为类似列表!