《流暢的python》第一章 Python數據模型

這一章主要講述如何用特殊方法去激活一些基本的對象操作,這些特殊方法的名字以兩個下劃線開頭,以兩個下劃線結尾(例如getitem)比如obj[key]的背後就是getitem方法,爲了能求得my_collection[key]的值,解釋器實際上會調用my_collection.getitem(key)。
本章的實例是一摞python風格的紙牌,很有嚼頭。
1.1紙牌實例

第一個示例1-1 建立了一個紙牌類:

import collections #【1】

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:#【2】
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):#【3】
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):#【4】
        return len(self._cards)

    def __getitem__(self, position):#【5】
        return self._cards[position]

【1】collections模塊,Python擁有一些內置的數據類型,比如str, int, list, tuple, dict等, collections模塊在這些內置數據類型的基礎上,提供了幾個額外的數據類型:
1.namedtuple(): 生成可以使用名字來訪問元素內容的tuple子類
2.deque: 雙端隊列,可以快速的從另外一側追加和推出對象
3.Counter: 計數器,主要用來計數
4.OrderedDict: 有序字典
5.defaultdict: 帶有默認值的字典
個人理解,這個namedtuple就有點像一個臨時表,例子中的Card是表名,rank和suit是字段名。
【2】一個短小精悍的類,這個對ranks和suits數據集進行了賦值,同時重構了init/len/getitem這三個方法。
【3】用self._cards創建一副完整的撲克牌實例
【4】計算這幅撲克牌的長度
【5】獲取每張撲克牌的位置


想一想,如果自己寫一副類似撲克牌,要多少代碼,而高手的代碼就是這麼簡潔高效。
用上面的代碼,可以輕鬆的得到一個紙牌對象,比如:

deck=FrenchDeck()
print(len(deck))

從一疊牌裏面抽取特定的一張也很簡單:deck[0]或者deck[-1],這裏面0是正向數第一張撲克牌,-1是逆向數第一張。
如果想隨機抽取一張紙牌也很簡單,用random.choice,代碼如下:

from random import choice
choice(deck)

可以看到,每次抽取的紙牌都不一樣,這就是造好的隨機數輪子(我們不需要自己去生成隨機數然後再挑選相應的對象,一個函數搞定),很方便。
因爲getitem方法把[]操作交給了self._cards列表,所以我們的deck類自動支持切片,例如查看一摞牌最上面3張和只看牌面是A的牌的操作:

deck[:3]
deck[12::13]#【6】

【6】這裏12的意思是索引號爲12的牌A,後面的13表示每隔13張抽一次。要把正副撲克牌顯示出來也很easy,可迭代的deck用一個for循環就可以便利:

for card in deck:
print(card)

反向迭代同樣很簡單:

for card in reversed(deck):
print(card)

接下來做一個稍微複雜的操作,排序,就是用點數來判定撲克牌的大小,2最小,A最大;同時黑桃最大,紅桃次之,方塊再次,梅花最小。

suit_values=dict(spades=3,hearts=2,diamonds=1,clubs=0)
def spades_high(card):
    rank_value=FrenchDeck.ranks.index(card.rank)#【7】
    return  rank_value * len(suit_values)+suit_values[card.suit]

下面調用spades_high函數對這摞牌進行升序排序

for card in sorted(deck,key=spades_high):
    print(card)

【7】這段代碼是不是有點難懂,慢慢研究一下。 rank_value的值等於FrenchDeck類下面ranks列表的索引值index(card.rank),card是一個Card類的對象,具有rank和suit兩種屬性。

1.2如何使用特殊方法
特殊方法的存在是爲了被Python解釋器調用的,程序員無需直接調用這些方法,也就是說沒有my_object.len()這種寫法,而應該使用len(my_object)。
本部分舉了一個二維向量的例子,下面是定義的一個Vector類,這個類重構了repr/abs/bool/add/mul這些特殊方法

from math import hypot
class Vector:

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Vector(%r, %r)' % (self.x, self.y)

    def __abs__(self):
        return hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

有了這個類,就可以實現向量加法了:

v1=Vector(2,4)
v2=Vector(2,1)
print(v1+v2)

調用abs函數也沒有問題:

v=Vector(3,4)
print(abs(v))

需要注意的是%r和%s的區別
%r用rper()方法處理對象
%s用str()方法處理對象
有些情況下,兩者處理的結果是一樣的,比如說處理int型對象。
本書的作者認爲,%和str.format這兩種格式化字符串的手段在本書中都會使用,但作者偏向於str.format,python程序員更喜歡%,這兩種形式並存的情況還會持續下去。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章