《流暢的Python》1.1一摞Python風(fēng)格的紙牌

數(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)getitemlen這兩個特殊方法,同時見識一下魔術(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)

1.2 如何使用特殊方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容