Fluent Python Charpter 1.1

Fluent Python Charpter 1.1

A Pythonic Card Deck

这个post在于介绍Python的Data Model。

书中的说法是,Data Model是framework。某些syntax或者built-in functions会触发解释器调用Python已经定义好的special methods。最直接的2个例子:

1
2
3
4
>>> len([0, 1]), [0, 1].__len__()
(2, 2)
>>> {'a': 0}['a'], {'a': 0}.__getitem__('a')
(0, 0)

len()触发的是list.__len__()dict[key]语法触发的就是dict.__getitem__(key)

这里再引用一个游戏王卡组的例子。

先用namedtuple定义卡牌,再实例化一张青眼白龙,typename是这类tuple的意义, field_names就是tuple中对应顺序元素的意义。

1
2
3
4
>>> import collections
>>> Card = collections.namedtuple(typename='Card', field_names=['Star', 'ATK'])
>>> Card(8, 3000)
Card(Star=8, ATK=3000)

然后定义有青眼白龙,黑暗魔术师和真红眼黑龙的卡组。我们还用切片的方式抽出卡组的第一张牌。

1
2
3
4
5
6
7
8
9
10
>>> class YuGiOhDeck:
>>> def __init__(self):
>>> self._cards = [Card(8, 3000), Card(7, 2500), Card(7, 2400)]
>>> def __len__(self):
>>> return len(self._cards)
>>> def __getitem__(self, position):
>>> return self._cards[position]
>>> deck = YuGiOhDeck()
>>> len(deck), deck[0]
(3, Card(Star=8, ATK=3000))

问题来了,我们抽牌不是神抽,那怎么抽卡组?所以需要写一个抽牌的method?

这个时候就体现出Data Model的好处。

1
2
3
>>> from random import choice
>>> choice(deck)
Card(Star=7, ATK=2500)

通过查看random.choice的源码,其实包含了三个步骤,

  1. 调用len(seq)获得序列的长度
  2. 产生一个[0, length-1]的随机整数i
  3. seq[i]切片

所以Data Model的好处就在于,提前定义了很多神奇的operations,我们只需要定义好它们所需要的special methods,就可以套用那些opeartions。

此外,因为我们已经定义好了__getitem__,所以我们还能通过切片神抽。

1
2
>>> deck[1]
Card(Star=7, ATK=2500)

定义好__getitem__,意味着YuGiOhDeck是iterable,于是reversed()sorted()in都支持了。

1
2
3
4
5
6
>>> list(reversed(deck))
[Card(Star=7, ATK=2400), Card(Star=7, ATK=2500), Card(Star=8, ATK=3000)]
>>> list(sorted(deck, key=lambda card: card[1]))
[Card(Star=7, ATK=2400), Card(Star=7, ATK=2500), Card(Star=8, ATK=3000)]
>>> Card(1, 1300) in deck
False

总结一下,我们不需要显式地定义一个新的类继承于某些已经基本的类,只需要通过定义special methods就能让我们定义的类可以使用到python的很多语言特性。