成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python學(xué)習(xí)之路20-數(shù)據(jù)模型

ad6623 / 1571人閱讀

摘要:前言數(shù)據(jù)模型其實(shí)是對(duì)框架的描述,它規(guī)范了這門語言自身構(gòu)件模塊的接口,這些模塊包括但不限于序列迭代器函數(shù)類和上下文管理器。上述類實(shí)現(xiàn)了方法,它可用于需要布爾值的上下文中等。但多虧了它是特殊方法,我們也可以把用于自定義數(shù)據(jù)類型。

《流暢的Python》筆記。
本篇是Python進(jìn)階篇的開始。本篇主要是對(duì)Python特殊方法的概述。
1. 前言

數(shù)據(jù)模型其實(shí)是對(duì)Python框架的描述,它規(guī)范了這門語言自身構(gòu)件模塊的接口,這些模塊包括但不限于序列、迭代器、函數(shù)、類和上下文管理器。不管在哪種框架下寫程序,都會(huì)花費(fèi)大量時(shí)間去實(shí)現(xiàn)那些會(huì)被框架本身調(diào)用的方法,Python也不例外。Python解釋器碰到特殊句法時(shí),會(huì)使用特殊方法去激活一些基本的對(duì)象操作,這些特殊方法的名字以兩個(gè)下劃線開頭,以兩個(gè)下劃線結(jié)尾(所以特殊方法也叫雙下方法 dunder method),這些特殊方法名能讓自己編寫的對(duì)象實(shí)現(xiàn)和支持以下的語言構(gòu)架,并與之交互:

迭代、集合類、屬性訪問、運(yùn)算符重載、函數(shù)和方法的調(diào)用、對(duì)象的創(chuàng)建和銷毀、字符串表示形式和格式化、管理上下文(即with塊)。

下面通過一些例子來介紹常用的特殊方法。

2. Python風(fēng)格紙牌

首先介紹兩個(gè)特殊方法__getitem____len__這兩個(gè)特殊方法。以下代碼創(chuàng)建了一個(gè)紙牌類:

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)

第一個(gè)參數(shù)是元組名;第二個(gè)是該元組中含的屬性名;第三個(gè)參數(shù)表示在構(gòu)建該命名元組之前先打印出該命名元組的結(jié)構(gòu),如果在控制臺(tái)輸入第3行代碼,并置verboseTrue的話,會(huì)輸出該命名元組的內(nèi)部結(jié)構(gòu),實(shí)際上它是一個(gè)繼承自tuple的類,由于輸出過長(zhǎng),請(qǐng)大家自行實(shí)驗(yàn);如果該命名元組的元素名中有Python關(guān)鍵字,則需要置第四個(gè)參數(shù)為True,這些與關(guān)鍵字重名的元素名會(huì)被特殊處理。

用命名元組創(chuàng)建一個(gè)不帶方法的對(duì)象十分簡(jiǎn)單:

>>> from chapter20 import Card, FrenchDeck
>>> beer_card = Card("7", "diamonds")
>>> beer_card
Card(rank="7", suit="diamonds")

由于FrenchDeck實(shí)現(xiàn)了__getitem__方法,所以可以像操作ListTuple一樣操作FrenchDeck,比如隨機(jī)訪問,切片:

>>> 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")]

由于實(shí)現(xiàn)了該方法,FrenchDeck還是個(gè)可迭代對(duì)象,即可以用for循環(huán)對(duì)其訪問(也可以反向訪問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")

迭代通常是隱式的,譬如說一個(gè)集合類型沒有實(shí)現(xiàn)__contains__方法,那么in運(yùn)算符就會(huì)按順序做一次迭代搜索(調(diào)用__getitem__),于是in運(yùn)算符可以用在FrenchDeck上:

>>> Card("2", "spades") in deck
True

如果對(duì)上述deck變量調(diào)用sorted函數(shù),Python將按ASCII碼進(jìn)行排序,但這并不是撲克牌的正確排序,所以下面我們自定義排序方法:

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)

此時(shí)輸出的結(jié)果就是先按點(diǎn)數(shù)排序,再按花色排序。

3. 如何使用特殊方法

需要明確一點(diǎn),特殊方法的存在是為了給Python解釋器調(diào)用到,作為程序員并不需要調(diào)用他們,也即是說,沒有my_object.__len__()這種寫法,而應(yīng)該是len(my_object)。說到__len__方法,如果是Python內(nèi)置類型,CPython會(huì)抄個(gè)近路,該方法實(shí)際上會(huì)直接返回PyVarObject里的ob_size屬性,而PyVarObject是表示內(nèi)存中長(zhǎng)度可變的內(nèi)痔對(duì)象的C語言結(jié)構(gòu)體。

很多時(shí)候特殊方法的調(diào)用是隱式的,比如for i in x:這個(gè)語句,背后其實(shí)用的是iter(x),而這個(gè)函數(shù)的背后則是x.__iter__()方法,當(dāng)然前提是這個(gè)方法在x中被實(shí)現(xiàn)(如果沒被實(shí)現(xiàn)則會(huì)調(diào)用__getitem__方法)。

直接調(diào)用這個(gè)值比調(diào)用一個(gè)方法快很多。直接調(diào)用特殊方法的頻率應(yīng)該遠(yuǎn)遠(yuǎn)低于你去實(shí)現(xiàn)它們的次數(shù)。

通過內(nèi)置的函數(shù)(例如leniter,str等)來使用特殊方法是最好的選擇。這些內(nèi)置函數(shù)不僅會(huì)調(diào)用特殊方法,通常還提供額外的好處,而且對(duì)于內(nèi)置的類來說,它們的速度更快。

還有一點(diǎn)值得注意:不要想當(dāng)然地隨意添加特殊方法,比如__foo__之類的,因?yàn)殡m然現(xiàn)在這個(gè)名字沒有被Python內(nèi)部使用,以后就不一定了。

3.1 自定義向量Vector

使用5個(gè)特殊方法實(shí)現(xiàn)Vector的字符串輸出,取絕對(duì)值(如果是復(fù)數(shù)則是取模),返回布爾值,加法和數(shù)乘等運(yù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)

    # 在Python中,只有0,NULL才是False,其余均為True
    def __bool__(self):
        # 更簡(jiǎn)單的寫法是:
        # return bool(self.x or self.y)
        return bool(abs(self))
    
    # 實(shí)現(xiàn)加法運(yùn)算符重載
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    # 實(shí)現(xiàn)乘法運(yùn)算符重載,這里是數(shù)乘,且還沒有實(shí)現(xiàn)交換律(需要實(shí)現(xiàn)__rmul__方法)
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

Python有一個(gè)內(nèi)置函數(shù)叫做repr。該函數(shù)通過特殊方法__repr__來得到一個(gè)對(duì)象的字符串表示形式,如果沒有該特殊方法,當(dāng)我們?cè)诳刂婆_(tái)打印一個(gè)向量對(duì)象時(shí),得到的字符串可能是

# 代碼:
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ù)打印一個(gè)對(duì)象的時(shí)候才被調(diào)用,它返回的字符串對(duì)終端用戶友好。如果只想實(shí)現(xiàn)這兩個(gè)特殊方法中的一個(gè),__repr__是更好的選擇,因?yàn)槿绻粋€(gè)對(duì)象沒有__str__函數(shù),Python又需要調(diào)用它時(shí),解釋器會(huì)用__repr__代替。

上述Vector類實(shí)現(xiàn)了__bool__方法,它可用于需要布爾值的上下文中(if, while, and, or, not等)。默認(rèn)情況下,我們自己定義的類的實(shí)例總被認(rèn)為是True,除非重寫了這個(gè)類的__bool____len__方法。bool(x)的背后是調(diào)用x.__bool__();如果不存在__bool__方法,那么bool(x)會(huì)嘗試調(diào)用x.__len__(),如果該方法返回0,則bool返回False,否則返回True

3.2 為什么len不是普通方法

“實(shí)用勝于純粹”(Python之禪里的一句話)。len之所以不是一個(gè)普通方法,是為了讓Python自帶的數(shù)據(jù)結(jié)構(gòu)可以走后門,abs也是同理。但多虧了它是特殊方法,我們也可以把len用于自定義數(shù)據(jù)類型。這種處理方式在保持內(nèi)置類型的效率和保證語言的一致性之間找到了一個(gè)平衡點(diǎn),也印證了“Python之禪”中的另一句話:“不能讓特例特殊到考試破壞既定規(guī)則”。

4. 總結(jié)

通過實(shí)現(xiàn)特殊方法,自定義數(shù)據(jù)類型可以表現(xiàn)得跟內(nèi)置類型一樣,從而讓我們寫出更具Python風(fēng)格(Pythonic)的代碼。后面的內(nèi)容將圍繞更多的特殊方法展開。


迎大家關(guān)注我的微信公眾號(hào)"代碼港" & 個(gè)人網(wǎng)站 www.vpointer.net ~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/44719.html

相關(guān)文章

  • Python學(xué)習(xí)之路17-Django入門

    摘要:編程從入門到實(shí)踐筆記。執(zhí)行命令后,項(xiàng)目的根目錄下會(huì)多出一個(gè)名為的數(shù)據(jù)庫文件。下面創(chuàng)建一個(gè)主題類用戶學(xué)習(xí)的主題返回模型的字符串表示類是中的一個(gè)定義了模型基本功能的類。這種交互式環(huán)境稱為,常用語測(cè)試項(xiàng)目和排除故障。 《Python編程:從入門到實(shí)踐》筆記。從本篇開始將是該書的最后一個(gè)項(xiàng)目,將用3篇文章來介紹Django的基礎(chǔ)。完成一個(gè)學(xué)習(xí)筆記的小網(wǎng)站。 1. 前言 在本篇中,我們將: 用...

    shadowbook 評(píng)論0 收藏0
  • Python學(xué)習(xí)之路24-一等函數(shù)

    摘要:函數(shù)內(nèi)省的內(nèi)容到此結(jié)束。函數(shù)式編程并不是一個(gè)函數(shù)式編程語言,但通過和等包的支持,也可以寫出函數(shù)式風(fēng)格的代碼。 《流暢的Python》筆記。本篇主要講述Python中函數(shù)的進(jìn)階內(nèi)容。包括函數(shù)和對(duì)象的關(guān)系,函數(shù)內(nèi)省,Python中的函數(shù)式編程。 1. 前言 本片首先介紹函數(shù)和對(duì)象的關(guān)系;隨后介紹函數(shù)和可調(diào)用對(duì)象的關(guān)系,以及函數(shù)內(nèi)省。函數(shù)內(nèi)省這部分會(huì)涉及很多與IDE和框架相關(guān)的東西,如果平時(shí)...

    wind3110991 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<