這一章主要講述如何用特殊方法去激活一些基本的對象操作,這些特殊方法的名字以兩個下劃線開頭,以兩個下劃線結尾(例如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程序員更喜歡%,這兩種形式並存的情況還會持續下去。