python数据模型,即“python data model”。也有很多人称之为“python object model”。维基百科上对object model的定义是:“一个编程语言中,对象的特性”。python数据模型也就是python中的对象区别于其他编程语言的特性。

本文大多内容是对《Fluent Python》内容的整理,加上一些自己的理解。可以看作是笔记。

下面将用一个“扑克牌类(FrenchDeck)”的例子来展现python data model。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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]

FrenchDeck中,实现了__len____getitem__。调用函数len()时会调用__len__。访问下标时,会调用__getitem__。于是我们可以像下面这样做:

1
2
3
4
5
6
7
8
9
10
deck = FrenchDeck()
print('0#', len(deck)) # 牌的数量
print('1#', deck[0])
print('2#', deck[-1])
"""输出
0# 52
1# Card(rank='2', suit='spades')
2# Card(rank='A', suit='hearts')
"""

但事实上远远不止如此。由于实现__getitem__的缘故,现在FrenchDeck的对象可以切片、迭代、甚至用标准库中的一些函数!

1
2
3
4
5
6
7
print('1#',deck[:3]) # 切片
for card in deck: # 迭代
print(card)
from random import choice
print(choice(deck)) # 使用random.choice 随机选一张牌
print(choice(deck))

用关键词 in 时会调用 __contains__() 。如果没有实现该接口,则默认会以遍历整个序列判断相等的方式来实现。由于实现了__getitem__,对象deck是可遍历的,因此可以像下面这样做:

1
2
3
4
5
Card('Q','hearts') in deck
"""输出
True
"""

到这里,基本已经了解了python数据模型。可以看到,python这种特性有两个好处:

  • 类的使用者不需要记忆任何标准操作的函数名。例如:“如何知道对象有有多少个元素?是.size()还是.length()?”
  • 能很大程度的利用python标准库。避免重复造轮子。例如:random.choice

实际上,__len____getitem__()__contains__()就类似于C++中的运算符重载。len()[]in都是(或可以看作是)运算符。

python中运算符也确实是通过这种方式重载的,例如:__neg__->-__add__->+

另外两个我认为比较重要的函数:

  • __repr__,需要返回一个字符串。对象被转换成字符串时调用。例如:print('%r'%(deck)),就会调用__repr__
  • __bool__,需要返回True或False。当使用ifwhileand等需用bool类型的控制语句或运算符时会调用。

其他的就不再一一列举,可以看下面两个表。

python special method

reference:
《Fluent Python》CHAPTER 1 The Python Data Model