數(shù)據(jù)模型其實是對Python框架的描述,它規(guī)范了這門語言自身構(gòu)建模塊的接口,這些模塊包括但不限于序列、迭代器、函數(shù)、類和上下文管理器。
不管在哪種框架下寫程序,都會花費大量時間去實現(xiàn)那些會被框架本身調(diào)用的方法,Python也不例外。Python解釋器碰到特殊的句法時,會使用特殊方法去激活一些基本的對象操作,這些特殊方法的名字以兩個下劃線開頭,以兩個下劃線結(jié)尾(例如getitem),比如obj[key]的背后就是getitem方法。
魔術(shù)方法是特殊方法的昵稱。
1.1 一摞Python風(fēng)格的紙牌
接下來我們要用一個非常簡單的例子來展示如何實現(xiàn)getitem和len這兩個特殊方法,同時見識一下魔術(shù)方法的強大。
下面的代碼建立了一個紙牌類:
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]
對于上面的代碼中的namedtuple你可能不太熟悉,不過通過下面的例子相信你一下子就明白了:
beer_card = Card('7','diamonds')
print (beer_card)
#Card(rank='7', suit='diamonds')
print (beer_card.rank,beer_card.suit)
#7 diamonds
print (beer_card[0],beer_card[1])
#7 diamonds
rank,suit = beer_card
print (rank,suit)
#7 diamonds
namedtuple就是有命名字段的元組結(jié)構(gòu),類似于元組,又類似于dict。
好了,言歸正傳,我們主要還是關(guān)注FrenchDeck這個類,首先,它跟任何標(biāo)準(zhǔn)Python集合類型一樣,可以用len()函數(shù)來查看一疊牌有多少張:
deck = FrenchDeck()
print (len(deck))
#52
從一疊牌中抽出一張紙牌,比如第一張或者最后一張,非常容易,使用下面的方法,它本質(zhì)通過調(diào)用getitem方法來實現(xiàn):
print (deck[0])
#Card(rank='2', suit='spades')
print (deck[-1])
#Card(rank='A', suit='hearts')
我們?nèi)绻胍獜闹须S機抽取一張牌,需要重寫一個方法么,不需要,因為Python已經(jīng)給我們提供了一個random.choice方法:
from random import choice
print (choice(deck))
#Card(rank='4', suit='clubs')
print (choice(deck))
#Card(rank='5', suit='spades')
現(xiàn)在我們可以體會到通過實現(xiàn)特殊方法來利用Python數(shù)據(jù)模型的兩個好處:
(1)作為你的類的用戶,他們不必去記住標(biāo)準(zhǔn)操作的各式名稱(例如如何得到元素的總數(shù),是size()還是length())
(2)可以更加方便的利用Python的標(biāo)準(zhǔn)庫,比如random.choice函數(shù),從而不用重新發(fā)明輪子。
好戲還在后面。
因為getitem方法把[]操作交給了self._cards類表,所以我們的deck類支持切片操作:
print(deck[:3])
#[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
print (deck[12::13])
#[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
另位,僅僅實現(xiàn)了getitem方法,我們的這一摞牌就變成可迭代的了:
for card in deck[:3]:
print (card)
# Card(rank='2', suit='spades')
# Card(rank='3', suit='spades')
# Card(rank='4', suit='spades')
for card in reversed(deck[:3]):
print (card)
# Card(rank='4', suit='spades')
# Card(rank='3', suit='spades')
# Card(rank='2', suit='spades')
迭代通常是隱式的,譬如說一個集合類型沒有實現(xiàn)contains方法,那么in運算符就會按順序做一次迭代搜索:
print (Card('Q','hearts') in deck)
#True
那么排序呢?我們按照常規(guī),用點數(shù)來判斷撲克牌的大小,2最小、A最大,同時加上花色的判定,黑桃最大、紅桃次之、方塊再次、梅花最小。下面就是按照這個規(guī)則給撲克牌排序的函數(shù)。
suit_values = dict(spades=3,hearts=2,diamonds=1,clubs=0)
def spades_high(card):
rank_value = FrenchDeck.ranks.index(card.rank)
return rank_value * len(suit_values) + suit_values[card.suit]
for card in sorted(deck,key=spades_high):
print (card)