摘要:前言數(shù)據(jù)模型其實是對框架的描述,它規(guī)范了這門語言自身構(gòu)件模塊的接口,這些模塊包括但不限于序列迭代器函數(shù)類和上下文管理器。上述類實現(xiàn)了方法,它可用于需要布爾值的上下文中等。但多虧了它是特殊方法,我們也可以把用于自定義數(shù)據(jù)類型。
《流暢的Python》筆記。1. 前言
本篇是Python進階篇的開始。本篇主要是對Python特殊方法的概述。
數(shù)據(jù)模型其實是對Python框架的描述,它規(guī)范了這門語言自身構(gòu)件模塊的接口,這些模塊包括但不限于序列、迭代器、函數(shù)、類和上下文管理器。不管在哪種框架下寫程序,都會花費大量時間去實現(xiàn)那些會被框架本身調(diào)用的方法,Python也不例外。Python解釋器碰到特殊句法時,會使用特殊方法去激活一些基本的對象操作,這些特殊方法的名字以兩個下劃線開頭,以兩個下劃線結(jié)尾(所以特殊方法也叫雙下方法 dunder method),這些特殊方法名能讓自己編寫的對象實現(xiàn)和支持以下的語言構(gòu)架,并與之交互:
迭代、集合類、屬性訪問、運算符重載、函數(shù)和方法的調(diào)用、對象的創(chuàng)建和銷毀、字符串表示形式和格式化、管理上下文(即with塊)。
下面通過一些例子來介紹常用的特殊方法。
2. Python風(fēng)格紙牌首先介紹兩個特殊方法__getitem__和__len__這兩個特殊方法。以下代碼創(chuàng)建了一個紙牌類:
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): # 嵌套循環(huán) 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]
namedtuple,即命名元組,類似于C/C++中的struct,定義如下:
collections.namedtuple(typename, field_names, verbose=False, rename=False)
第一個參數(shù)是元組名;第二個是該元組中含的屬性名;第三個參數(shù)表示在構(gòu)建該命名元組之前先打印出該命名元組的結(jié)構(gòu),如果在控制臺輸入第3行代碼,并置verbose為True的話,會輸出該命名元組的內(nèi)部結(jié)構(gòu),實際上它是一個繼承自tuple的類,由于輸出過長,請大家自行實驗;如果該命名元組的元素名中有Python關(guān)鍵字,則需要置第四個參數(shù)為True,這些與關(guān)鍵字重名的元素名會被特殊處理。
用命名元組創(chuàng)建一個不帶方法的對象十分簡單:
>>> from chapter20 import Card, FrenchDeck >>> beer_card = Card("7", "diamonds") >>> beer_card Card(rank="7", suit="diamonds")
由于FrenchDeck實現(xiàn)了__getitem__方法,所以可以像操作List或Tuple一樣操作FrenchDeck,比如隨機訪問,切片:
>>> deck = FrenchDeck() >>> len(deck) 52 >>> deck[0] Card(rank="2", suit="spades") >>> deck[-1] Card(rank="A", suit="hearts") >>> from random import choice >>> choice(deck) Card(rank="4", suit="clubs") >>> choice(deck) Card(rank="J", suit="clubs") >>> 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")]
由于實現(xiàn)了該方法,FrenchDeck還是個可迭代對象,即可以用for循環(huán)對其訪問(也可以反向訪問reversed):
>>> for card in deck: >>> ... print(card) Card(rank="2", suit="spades") Card(rank="3", suit="spades") Card(rank="4", suit="spades") -- snip -- Card(rank="Q", suit="hearts") Card(rank="K", suit="hearts") Card(rank="A", suit="hearts")
迭代通常是隱式的,譬如說一個集合類型沒有實現(xiàn)__contains__方法,那么in運算符就會按順序做一次迭代搜索(調(diào)用__getitem__),于是in運算符可以用在FrenchDeck上:
>>> Card("2", "spades") in deck True
如果對上述deck變量調(diào)用sorted函數(shù),Python將按ASCII碼進行排序,但這并不是撲克牌的正確排序,所以下面我們自定義排序方法:
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)
此時輸出的結(jié)果就是先按點數(shù)排序,再按花色排序。
3. 如何使用特殊方法需要明確一點,特殊方法的存在是為了給Python解釋器調(diào)用到,作為程序員并不需要調(diào)用他們,也即是說,沒有my_object.__len__()這種寫法,而應(yīng)該是len(my_object)。說到__len__方法,如果是Python內(nèi)置類型,CPython會抄個近路,該方法實際上會直接返回PyVarObject里的ob_size屬性,而PyVarObject是表示內(nèi)存中長度可變的內(nèi)痔對象的C語言結(jié)構(gòu)體。
很多時候特殊方法的調(diào)用是隱式的,比如for i in x:這個語句,背后其實用的是iter(x),而這個函數(shù)的背后則是x.__iter__()方法,當(dāng)然前提是這個方法在x中被實現(xiàn)(如果沒被實現(xiàn)則會調(diào)用__getitem__方法)。
直接調(diào)用這個值比調(diào)用一個方法快很多。直接調(diào)用特殊方法的頻率應(yīng)該遠(yuǎn)遠(yuǎn)低于你去實現(xiàn)它們的次數(shù)。
通過內(nèi)置的函數(shù)(例如len,iter,str等)來使用特殊方法是最好的選擇。這些內(nèi)置函數(shù)不僅會調(diào)用特殊方法,通常還提供額外的好處,而且對于內(nèi)置的類來說,它們的速度更快。
還有一點值得注意:不要想當(dāng)然地隨意添加特殊方法,比如__foo__之類的,因為雖然現(xiàn)在這個名字沒有被Python內(nèi)部使用,以后就不一定了。
3.1 自定義向量Vector使用5個特殊方法實現(xiàn)Vector的字符串輸出,取絕對值(如果是復(fù)數(shù)則是取模),返回布爾值,加法和數(shù)乘等運算:
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) # 在Python中,只有0,NULL才是False,其余均為True def __bool__(self): # 更簡單的寫法是: # return bool(self.x or self.y) return bool(abs(self)) # 實現(xiàn)加法運算符重載 def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) # 實現(xiàn)乘法運算符重載,這里是數(shù)乘,且還沒有實現(xiàn)交換律(需要實現(xiàn)__rmul__方法) def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar)
Python有一個內(nèi)置函數(shù)叫做repr。該函數(shù)通過特殊方法__repr__來得到一個對象的字符串表示形式,如果沒有該特殊方法,當(dāng)我們在控制臺打印一個向量對象時,得到的字符串可能是
# 代碼: v1 = Vector(2, 4) v2 = Vector(2, 1) print(v1 + v2) print(abs(v1)) print(v1 * 3) # 結(jié)果: Vector(4, 5) 4.47213595499958 Vector(6, 12)
__repr__與__str__的區(qū)別與聯(lián)系:前者方便我們調(diào)試和記錄日志,后者則是給終端用戶看的。后者是在str()函數(shù)被使用,或者是在print函數(shù)打印一個對象的時候才被調(diào)用,它返回的字符串對終端用戶友好。如果只想實現(xiàn)這兩個特殊方法中的一個,__repr__是更好的選擇,因為如果一個對象沒有__str__函數(shù),Python又需要調(diào)用它時,解釋器會用__repr__代替。
上述Vector類實現(xiàn)了__bool__方法,它可用于需要布爾值的上下文中(if, while, and, or, not等)。默認(rèn)情況下,我們自己定義的類的實例總被認(rèn)為是True,除非重寫了這個類的__bool__或__len__方法。bool(x)的背后是調(diào)用x.__bool__();如果不存在__bool__方法,那么bool(x)會嘗試調(diào)用x.__len__(),如果該方法返回0,則bool返回False,否則返回True。
3.2 為什么len不是普通方法“實用勝于純粹”(Python之禪里的一句話)。len之所以不是一個普通方法,是為了讓Python自帶的數(shù)據(jù)結(jié)構(gòu)可以走后門,abs也是同理。但多虧了它是特殊方法,我們也可以把len用于自定義數(shù)據(jù)類型。這種處理方式在保持內(nèi)置類型的效率和保證語言的一致性之間找到了一個平衡點,也印證了“Python之禪”中的另一句話:“不能讓特例特殊到考試破壞既定規(guī)則”。
4. 總結(jié)通過實現(xiàn)特殊方法,自定義數(shù)據(jù)類型可以表現(xiàn)得跟內(nèi)置類型一樣,從而讓我們寫出更具Python風(fēng)格(Pythonic)的代碼。后面的內(nèi)容將圍繞更多的特殊方法展開。
迎大家關(guān)注我的微信公眾號"代碼港" & 個人網(wǎng)站 www.vpointer.net ~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/44719.html
摘要:編程從入門到實踐筆記。執(zhí)行命令后,項目的根目錄下會多出一個名為的數(shù)據(jù)庫文件。下面創(chuàng)建一個主題類用戶學(xué)習(xí)的主題返回模型的字符串表示類是中的一個定義了模型基本功能的類。這種交互式環(huán)境稱為,常用語測試項目和排除故障。 《Python編程:從入門到實踐》筆記。從本篇開始將是該書的最后一個項目,將用3篇文章來介紹Django的基礎(chǔ)。完成一個學(xué)習(xí)筆記的小網(wǎng)站。 1. 前言 在本篇中,我們將: 用...
摘要:函數(shù)內(nèi)省的內(nèi)容到此結(jié)束。函數(shù)式編程并不是一個函數(shù)式編程語言,但通過和等包的支持,也可以寫出函數(shù)式風(fēng)格的代碼。 《流暢的Python》筆記。本篇主要講述Python中函數(shù)的進階內(nèi)容。包括函數(shù)和對象的關(guān)系,函數(shù)內(nèi)省,Python中的函數(shù)式編程。 1. 前言 本片首先介紹函數(shù)和對象的關(guān)系;隨后介紹函數(shù)和可調(diào)用對象的關(guān)系,以及函數(shù)內(nèi)省。函數(shù)內(nèi)省這部分會涉及很多與IDE和框架相關(guān)的東西,如果平時...
閱讀 1342·2021-09-24 10:26
閱讀 3655·2021-09-06 15:02
閱讀 604·2019-08-30 14:18
閱讀 576·2019-08-30 12:44
閱讀 3119·2019-08-30 10:48
閱讀 1936·2019-08-29 13:09
閱讀 1993·2019-08-29 11:30
閱讀 2279·2019-08-26 13:36