熱愛Python
Python是Guido van Rossum設計出來的讓使用者覺得如沐春風的一門程式語言。2020年11月12日,64歲的Python之父宣佈由於退休生活太無聊,自己決定加入Microsoft的DevDiv Team,致力於“確保更好地使用Python”。儘管在國內有些聲音在Diss著Python,認為它太簡單,只是個指令碼語言,但是它的發明者對Python的熱情,仍然激勵著我們堅持對Python的熱愛。
龜叔是所有程式語言發明者當中頭髮最多的這位。
奇蹟時刻
collection.len()
是面嚮物件語言的寫法,len(collection)
是Python語言的寫法,這種風格叫做Pythonic
。從前者到後者,就像變魔術一樣,一瞬間讓人眼前一亮。這個魔術就是Python魔法方法,或者叫雙下方法,它是用雙下劃線開頭和雙下劃線結尾的特殊方法,比如obj[key]
,Python直譯器實際上會轉換成obj.__getitem__(key)
來執行,但是使用者並無感知。
__getitem__
和__len__
__getitem__
用來獲取資料,__len__
用來返回長度,這2個魔法方法是Python基礎,我們通過一副撲克牌來了解:
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, position):
return self._cards[position]
本來我們對這副牌什麼都不能做,但是由於實現了__len__
,可以使用len()
函式檢視有多少張牌:
>>> len(deck)
52
由於實現了__getitem__
,可以使用中括號索引取值:
>>> deck[0]
Card(rank='2', suit='spades')
能進行切片:
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
能迭代:
>>> for card in deck: # doctest: +ELLIPSIS
... print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...
發現沒有,魔法方法是可以用來裝B的!別人寫個類只能get
、set
,你寫個類還能花式炫技,666。
Python魔法方法是給Python直譯器使用的,一般不需要直接呼叫,Python會自己去調,比如把
len(my_object)
寫成my_object.__len__()
,就弄巧成拙了。
魔法方法實現運算子
前面例子實現了取值和長度,接著再看一個例子,使用__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)
__add__
實現了加法:
>>> v1 = Vector(2, 4)
>>> v2 = Vector(2, 1)
>>> v1 + v2
Vector(4, 5)
__abs__
實現了絕對值:
>>> v = Vector(3, 4)
>>> abs(v)
5.0
__mul__
實現了乘法:
>>> v * 3
Vector(9, 12)
__repr__
實現了物件的字串表示:
Vector(4, 5)
否則得到的字串可能是
<Vector object at 0x10e100070>
。
__bool__
實現了布林值:
if Vector(4, 5):
return True
其他魔法方法
一篇文章是講不完魔法方法的,我們會在後續文章中,繼續探討如何使用和實現它們。
Tips
本小節內容是我看《流暢的Python》第一遍時記錄的知識點:
-
collections.namedtuple
可以用來建立只有少數屬性但沒有方法的物件,比如beer_card = Card('7', 'diamonds')
-
random.choice
和random.sample
不一樣的地方在於,sample是返回序列,choice是返回元素,當使用sample(list, 1)[0]的時候,不如直接使用choice(list)。 -
特殊方法的存在是為了被Python直譯器呼叫的。
-
PyVarObject是表示記憶體中長度可變的內建物件的C語言結構體。
list
或str
或bytearray
的__len__實際上返回的PyVarObject.ob_size屬性,這個比呼叫一個方法要快的多。 -
len之所以不是一個普通方法,是為了讓python自帶的資料結構可以走後門,abs也是同理。
-
很多時候呼叫__init__方法的目的是,在你自己的子類的__init__方法中呼叫超類的構造器。
-
abs,如果輸入是整數或者浮點數,它返回的是輸入值的絕對值;如果輸入是複數,那麼返回這個複數的模。
-
__repr__和__str__二選一的話,__repr__更好,因為如果一個物件沒有__str__函式,直譯器會用__repr__作為替代。
-
python物件的一個基本要求就是它得有合理的字串表示形式,這就是資料模型中存在特殊方法__repr__和__str__的原因。
-
為了判定一個值x為真還是為假,python會呼叫bool(x),它的背後是呼叫x.__bool__()。如果不存在,就會呼叫x.__len__(),返回0為Flase,非0為True。
-
python通過運算子過載這一模式提供了豐富的數值型別,除了內建那些,還有
decimal.Decimal
和fractions.Fraction
。
小結
本文是Python進階系列開篇,參考《流暢的Python》序章改寫而成。原書內容有深度有廣度,我選擇了其中的魔法方法知識點,作為切入,循序漸進學習。其實書中這一章節的副標題是“資料模型”,它是個什麼概念呢?
系列文章會不定期同步到線上電子書中,歡迎訪問檢視:
參考資料:
《流暢的Python》