學習python一段時間會發現,一直都是用python做業務邏輯。每次都是為了解決問題,而解決問題。而python中豐富的庫會讓我們欣喜,但是也可能讓我們變懶,真正對python的理解卻沒有增加多少。而fluent python是進階python非常好的一本書。
Python的資料模型
Let's be pythonic!
# 例子來自fluent python第4頁
import collections
# 用來構建一個只有屬性,沒有方法的簡單類,來代表撲克牌的號碼和花色。
Card = collections.namedtuple('Card', ['rank', 'suit'])
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, item):
return self._cards[item]複製程式碼
有了這些方法以後,我們就可以像正常操作列表一下操作我們的 FrenchDeck 。
deck = FrenchDeck()
len(deck) #輸出為52複製程式碼
由於提供了 __getitem__
方法,可以直接通過下標直接訪問對應的位置的元素,甚至可以像列表那樣進行切片操作和遍歷操作。
deck[0] # Card(rank='2', suit='spades')
deck[1] # Card(rank='A', suit='hearts')
deck[0:3] # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
for card in deck:
print card # 輸出省略複製程式碼
需要指出的是遍歷操作並不總是顯式的。如果一個集合沒有實現 __contains__
方法,則 in
操作就會進行順序遍歷操作。
至於排序操作,需要我們提供排序的依據,現在假設排序的是按照先看號碼,再看花色的順序,其中花色按照梅花,方塊,紅桃,黑桃的順序。所以可以按照這個順序,計算出每張牌的位置索引,如下:
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
# 輸出
Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
... (46 cards ommitted)
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')複製程式碼
通過實現 __len__
和 __getitem__
方法,我們就可以像操作 python 內建的資料型別那樣操作我們的資料模型。不過兩者依然有區別。在 CPython 中,當我們對內建的資料型別進行 len(x)
操作的時候,我們其實並沒有呼叫任何方法,只是取得 C 結構中的一個域。這使得我們可以高效的操作很多內建型別,如 str, list, memoryview 等等。
模擬數學型別
我們都在高中學過向量運算。
基本的向量操作包括,向量相加,向量求模,向量和標量相乘等等。然而我們希望用我們習慣的內建操作 +
abs
*
來進行這些運算,所以需要我們自定義的資料模型實現一些特殊方法。
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)
v1 + v2 # Vector(4,5)
v = Vector(3,4)
abs(v) # 5.0
v * 3 # Vector(9, 12)
abs(v * 3) # 15.0複製程式碼
值得指出的是 __repr__
方法,如果不實現這個方法的話。直接列印一個 Vector
物件可能會輸出
<Vector object at 0x10e100070>.複製程式碼
而定義了這個方法後,輸出就變得相當易讀
Vector(3,4)複製程式碼
當然,還可以定義 __str__
來自定義str(x)
的行為。如果只想實現一個函式的話,建議實現 __repr__
函式,因為當沒有 __str__
,會呼叫__repr__
作為備用。