1. 程式人生 > >《流暢的python》讀書筆記,第一章:python資料模型

《流暢的python》讀書筆記,第一章:python資料模型

這本書上來就講了魔法方法,也叫雙下方法、特殊方法,通過兩個例子對讓讀者瞭解了雙下方法的用法,更重要的是,讓我一窺Python的語言風格和給使用者的自由度。

第一個例子:一摞Python風格的紙牌:

import collections

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


class FrenchCard:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'clubs diamonds hearts spades
'.split() def __init__(self): self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position] deck = FrenchCard() suits_values = dict(clubs=0, diamonds=1, hearts=2, spades=3)
def high_spades(card): ranks_value = FrenchCard.ranks.index(card.rank) return ranks_value * len(suits_values) + suits_values[card.suit] for card in sorted(deck, key=high_spades): print(card)

這個例子讓我瞭解到python的許多方法都是由雙下方法構成了,比如在這個例子中,如果列印print(deck[0]),會顯示Card(rank='2', suit='clubs’),這是因為類裡面定義了__getitem__方法,如果沒有這個方法的話,就會報錯,而且,僅僅實現了__getitem__就可以迭代和反向迭代了。我看了下,list這個類裡面也有__getitem__這個方法,那麼,如果我們有需求的話,可以定製list裡的__getitem__的方法(通過整合和派生)。不過,len這個方法有點特殊,我會在後面講到哪裡特殊。   通過__len__和__getitem__, FrenchDeck這個類,就和Python現有的序列資料型別一樣,可以提現出Python的核心語言特性(例如迭代和切片)   另外,namedtuple是一個很好用的函式,用來建立一個只有少量屬性,沒有方法的類,比單獨建立一個類方便多了,感覺和__slots__一點相似,但是沒有在實戰中用過,以後用到了再去深入瞭解吧!它的因為解釋我覺得很清楚: Returns a new subclass of tuple with named field。
 

如何使用特殊方法

特殊方法是為了讓Python直譯器呼叫的,你自己並不需要呼叫它們。也就是說沒有 my_object.__len__() 這種寫法, 而應該使用 len(my_object)。在執行 len(my_object) 的時候,如果 my_object 是一個自定義類的物件,那麼 Python 會自己去呼叫其中由 你實現的 __len__ 方法。除非使用超程式設計,否則你的程式碼通常無需使用特殊方法。   在處理python內建型別的時候,比如列表、字串、位元組等,CPython會走後門,__len__實際是直接返回PyVarObject裡的ob_size屬性,這個是通過C語言實現的,速度比呼叫這個方法快很多。   注意,只有是Python自帶的資料結構才走這個後門,如果是自己定義的類裡有__len__方法的話,是不會走這個後門的。這體現了Python之禪,實用勝於純粹。len之所以不是一個普通方法(CPython直接從一個C結構體裡讀取物件的長度),原因是因為我們在程式設計的時候會大量使用Python自帶的資料型別,而同時我們又可以把len用於自定義資料型別。這種處理方式在保持內建型別的效率和 保證語言的一致性之間找到了一個平衡點,也印證了“Python 之禪”中的 另外一句話:“不能讓特例特殊到開始破壞既定規則。”  

第二個例子:二維向量(vector)

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)  # x * x + y * 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(1, 2)
v2 = Vector(2, 3)
print(v1)  # Vector(1,2)

print(v1 + v2)  # Vector(3,5)

v3 = Vector(0, 0)
print(bool(v3))  # False

這個例子使用了6個特殊方法,但是這6個特殊方法並不會在這個類自身的程式碼中使用,一般只有Python直譯器會頻繁地直接呼叫這些方法。   repr方法能把一個物件用字串的形式表示,在老的使用 % 符號的字串格式中,這個函式返回的結果用來代 替 %r 所代表的物件。當列印v1的時候會顯示Vector(1,2),如果沒有__repr__就會顯示v1這個物件的記憶體地址。__repr__和__str__不同的是,使用互動器的時候只有__repr__方法會生效,使用print的時候會先找__str__,沒有__str__在去找__repr__。   __add__和__mul__讓Vector的物件可以在不改變操作物件的情況下產出一個新的值,實現加法和乘法的運算。   我們自己定義的__bool__的意思是如果一個向量的模是0(x,y都為0),就返回False。bool(v3)呼叫的就是v3.__bool__(),如果我們沒有定義__bool__的話,那麼bool(v3)就會去嘗試呼叫v3.__len __(),如果返回0的話,就是False,否則就是True。   下面這種寫法會讓Vector.__bool__更高效,不過不如第一個好理解,卻能省掉從 abs 到 __abs__ 到平方再到平方根這 些中間步驟。 
def __bool__(self):
        return bool(self.x,self.y)

Ruby和Python都支援這些特殊方法,Ruby社群稱之為魔法方法。Javascript在這方面無法是趕不上Python和Ruby的。