魔法方法推開Python進階學(xué)習(xí)大門
本文轉(zhuǎn)載自微信公眾號「PythonMind」,作者dongfanger。轉(zhuǎn)載本文請聯(lián)系PythonMind公眾號。
熱愛Python
Python是Guido van Rossum設(shè)計出來的讓使用者覺得如沐春風(fēng)的一門編程語言。2020年11月12日,64歲的Python之父宣布由于退休生活太無聊,自己決定加入Microsoft的DevDiv Team,致力于“確保更好地使用Python”。盡管在國內(nèi)有些聲音在Diss著Python,認為它太簡單,只是個腳本語言,但是它的發(fā)明者對Python的熱情,仍然激勵著我們堅持對Python的熱愛。
龜叔是所有編程語言發(fā)明者當(dāng)中頭發(fā)最多的這位。
奇跡時刻
collection.len()是面向?qū)ο笳Z言的寫法,len(collection)是Python語言的寫法,這種風(fēng)格叫做Pythonic。從前者到后者,就像變魔術(shù)一樣,一瞬間讓人眼前一亮。這個魔術(shù)就是Python魔法方法,或者叫雙下方法,它是用雙下劃線開頭和雙下劃線結(jié)尾的特殊方法,比如obj[key],Python解釋器實際上會轉(zhuǎn)換成obj.__getitem__(key)來運行,但是使用者并無感知。
__getitem__和__len
____getitem__用來獲取數(shù)據(jù),__len__用來返回長度,這2個魔法方法是Python基礎(chǔ),我們通過一副撲克牌來了解:
- 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]
本來我們對這副牌什么都不能做,但是由于實現(xiàn)了__len__,可以使用len()函數(shù)查看有多少張牌:
- >>> len(deck)
- 52
由于實現(xiàn)了__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')
- ...
發(fā)現(xiàn)沒有,魔法方法是可以用來裝B的!別人寫個類只能get、set,你寫個類還能花式炫技,666。
Python魔法方法是給Python解釋器使用的,一般不需要直接調(diào)用,Python會自己去調(diào),比如把len(my_object)寫成my_object.__len__(),就弄巧成拙了。
魔法方法實現(xiàn)運算符
前面例子實現(xiàn)了取值和長度,接著再看一個例子,使用__repr__、__abs__、__bool__、__add__、__mul__,實現(xiàn)運算符:
- 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__實現(xiàn)了加法:
- >>> v1 = Vector(2, 4)
- >>> v2 = Vector(2, 1)
- >>> v1 + v2
- Vector(4, 5)
__abs__實現(xiàn)了絕對值:
- >>> v = Vector(3, 4)
- >>> abs(v)
- 5.0
__mul__實現(xiàn)了乘法:
- >>> v * 3
- Vector(9, 12)
__repr__實現(xiàn)了對象的字符串表示:
- Vector(4, 5)
否則得到的字符串可能是
__bool__實現(xiàn)了布爾值:
- if Vector(4, 5):
- return True
其他魔法方法
一篇文章是講不完魔法方法的,我們會在后續(xù)文章中,繼續(xù)探討如何使用和實現(xiàn)它們。
Tips
本小節(jié)內(nèi)容是我看《流暢的Python》第一遍時記錄的知識點:
- collections.namedtuple可以用來創(chuàng)建只有少數(shù)屬性但沒有方法的對象,比如
- beer_card = Card('7', 'diamonds')
- 2.random.choice和random.sample不一樣的地方在于,sample是返回序列,choice是返回元素,當(dāng)使用sample(list, 1)[0]的時候,不如直接使用choice(list)。
- 特殊方法的存在是為了被Python解釋器調(diào)用的。
- PyVarObject是表示內(nèi)存中長度可變的內(nèi)置對象的C語言結(jié)構(gòu)體。list或str或bytearray的__len__實際上返回的PyVarObject.ob_size屬性,這個比調(diào)用一個方法要快的多。
- len之所以不是一個普通方法,是為了讓python自帶的數(shù)據(jù)結(jié)構(gòu)可以走后門,abs也是同理。
- 很多時候調(diào)用__init__方法的目的是,在你自己的子類的__init__方法中調(diào)用超類的構(gòu)造器。
- abs,如果輸入是整數(shù)或者浮點數(shù),它返回的是輸入值的絕對值;如果輸入是復(fù)數(shù),那么返回這個復(fù)數(shù)的模。
- __repr__和__str__二選一的話,__repr__更好,因為如果一個對象沒有__str__函數(shù),解釋器會用__repr__作為替代。
- python對象的一個基本要求就是它得有合理的字符串表示形式,這就是數(shù)據(jù)模型中存在特殊方法__repr__和__str__的原因。
- 為了判定一個值x為真還是為假,python會調(diào)用bool(x),它的背后是調(diào)用x.__bool__()。如果不存在,就會調(diào)用x.__len__(),返回0為Flase,非0為True。
- python通過運算符重載這一模式提供了豐富的數(shù)值類型,除了內(nèi)置那些,還有decimal.Decimal和fractions.Fraction。