摘要:比較運算符方法有六個比較運算符。根據(jù)文檔,其映射工作如下第七章創(chuàng)建數(shù)字我們會再次回到比較運算符這塊。同一個類的對象的比較實現(xiàn)我們來看看一個簡單的同一類的比較通過觀察一個更完整的類現(xiàn)在我們已經(jīng)定義了所有六個比較運算符。
__bool__()方法注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python
Python對假有個很好的定義。參考手冊列出了大量的值來被檢測為False。這包括諸如:False、0、""、[]、()、{}。大多數(shù)其他對象將被檢測為True。
通常,我們會用一個簡單的語句檢查一個對象“不空”,如下所示:
if some_object: process(some_object)
隱藏在內(nèi)部的是內(nèi)置函數(shù)__bool__()的工作。這個函數(shù)依賴于一個給定對象的__bool__()方法。
默認的__bool__()方法返回True。我們可以看如下代碼:
>>> x = object() >>> bool(x) True
對于大多數(shù)類,這是非常有效的。大多數(shù)對象不會是False。然而,對于集合這是不合適的??占蠎撓喈斢?b>False。非空集合則返回True。我們可能需要添加一個這樣的方法到我們的Deck對象。
如果我們包裝一個列表,我們需要如下操作:
def __bool__(self): return bool(self._cards)
它委托布爾函數(shù)到內(nèi)部_cards集合。
如果我們擴展列表,我們需要如下操作:
def __bool__(self): return super().__bool__(self)
它委托到__bool__()函數(shù)的超類定義。
在這兩種情況下,我們特意地委托布爾檢測。在包裝那個例子,我們委托給集合。在擴展的例子,我們委托給超類。無論哪種方式,包裝或擴展,空集合將是False。這將給我們一種途徑去看Deck對象是否已經(jīng)完全處理完且是空的。
我們可以按照如下代碼片段所示去做:
d = Deck() while d: card= d.pop() # process the card
此循環(huán)將處理所有的牌,在整副牌都沒有了的時候不會出現(xiàn)IndexError異常。
__bytes__()方法有時候會出現(xiàn)將一個對象轉(zhuǎn)換成字節(jié)的情況。我們將在第2部分《持久化和序列化》中詳細看看相關(guān)內(nèi)容。
在最常見的情況下,應用程序可以創(chuàng)建一個字符串表示,Python IO類內(nèi)置的編碼功能可以將字符串轉(zhuǎn)換成字節(jié)。幾乎任何情況下都能完成的很好。唯一的例外是當我們定義了一種新的字符串。在這種情況下,我們需要定義該字符串編碼。
bytes()函數(shù)可以做很多事情,但這取決于它的參數(shù):
bytes(integer):返回給定整數(shù)個0x00的不可變字節(jié)對象。
bytes(string):將給定字符串編碼成字節(jié)。額外的編碼參數(shù)和錯誤處理將定義編碼處理的細節(jié)。
bytes(something):這將調(diào)用something.__bytes__()來創(chuàng)建一個字節(jié)對象。這里將不會使用編碼或錯誤參數(shù)。
基本object類沒有定義__bytes__()。這意味著我們的類默認不提供__bytes__()方法。
有一些特殊的情況下,我們可能需要有一個在寫入到文件之前被直接編碼到字節(jié)中的對象。通常是簡單的字符串并允許str類型為我們生成字節(jié)。在處理字節(jié)時,重要的是要注意,沒有簡單的方法從文件或接口來解碼。內(nèi)置的bytes類只會解碼字符串,不是我們獨有的新對象。我們可能需要從字節(jié)解碼來解析字符串?;蛘?,我們可能需要使用struct模塊顯式地解析字節(jié),通過解析好的值創(chuàng)建獨有對象。
我們看看編碼和解碼Card成字節(jié)。有52個牌值,每張牌可以打包到一個字節(jié)。然而,我們已經(jīng)選擇使用一個字符代表suit和一個字符來表示rank。此外,我們需要正確地重構(gòu)Card子類,所以我們必須編碼幾件事情:
Card(AceCard, NumberCard, FaceCard)子類
子類定義的__init__()的參數(shù)
注意,我們的替代方法__init__()將一個牌值轉(zhuǎn)換成一個字符串,失去原來的數(shù)值。為了一個可逆的字節(jié)編碼,我們需要重構(gòu)這個原始牌值。
下面是__bytes__()的實現(xiàn),它返回一個utf-8編碼的Cards類、rank和suit:
def __bytes__(self): class_code = self.__class__.__name__[0] rank_number_str = {"A": "1", "J": "11", "Q": "12", "K": "13"}. get(self.rank, self.rank) string = "("+" ".join([class_code, rank_number_str, self.suit,]) + ")" return bytes(string, encoding="utf8")
以上通過創(chuàng)建一個Card對象的字符串表示,然后編碼字符串到字節(jié)才能起作用。這通常是最簡單、最靈活的方法。
當我們有一堆字節(jié)的時候,可以解碼字符串,然后將字符串解析到新的Card對象。下面是一個可以用于從字節(jié)創(chuàng)建一個Card對象的方法:
def card_from_bytes(buffer): string = buffer.decode("utf8") assert string[0] == "(" and string[-1] == ")" code, rank_number, suit = string[1:-1].split() class_ = {"A": AceCard, "N": NumberCard, "F": FaceCard}[code] return class_(int(rank_number), suit)
在前面的代碼中,我們將字節(jié)解碼為一個字符串。然后我們將該字符串解析為各個值。從這些值,我們可以定位類且構(gòu)建原始Card對象。
我們可以構(gòu)建Card對象的字節(jié)表示,如下:
b = bytes(someCard)
我們可以通過字節(jié)重構(gòu)Card對象,如下:
someCard = card_from_bytes(b)
重要的是要注意,外部字節(jié)表示通常是具有挑戰(zhàn)性的設(shè)計。我們創(chuàng)建一個對象狀態(tài)表示。Python已經(jīng)有很多對我們類定義工作的很好的表示。
通常是使用pickle或json模塊比發(fā)明低級的字節(jié)來表示一個對象要更好。這是第九章《序列化和存儲JSON、YAML、Pickle、CSV和XML》的主要內(nèi)容。
比較運算符方法Python有六個比較運算符。這些操作符有特殊的方法實現(xiàn)。根據(jù)文檔,其映射工作如下:
x < y calls x.__lt__(y)
x <= y calls x.__le__(y)
x == y calls x.__eq__(y)
x != y calls x.__ne__(y)
x > y calls x.__gt__(y)
x >= y calls x.__ge__(y)
第七章《創(chuàng)建數(shù)字》我們會再次回到比較運算符這塊。
有一些關(guān)于哪個操作符被真實實現(xiàn)的額外規(guī)則。這些規(guī)則是基于左邊對象的類所需的特殊方法。如果沒有,Python可以改變順序來嘗試另一種操作。
這里有兩個基本規(guī)則
首先,左邊的操作數(shù)是由操作符的實現(xiàn)方法來檢查的:A < B意味著A.__lt__(B)。
第二,右邊的操作數(shù)是由相反的操作符的實現(xiàn)方法來檢查的:A < B意味著B.__gt__(A)。
罕見的例外發(fā)生在右操作數(shù)是左操作數(shù)的一個子類;然后,右操作數(shù)是第一個被檢查的,允許子類覆蓋超類。
我們可以看到這是如何工作的當一個類只有一個操作符的時候,然后供其他操作符使用。
以下是我們可以使用的部分類:
class BlackJackCard_p: def __init__(self, rank, suit): self.rank = rank self.suit = suit def __lt__(self, other): print("Compare {0} < {1}".format(self, other)) return self.rank < other.rank def __str__(self): return "{rank}{suit}".format(**self.__dict__)
這是21點的比較規(guī)則,花色不重要。我們省略了比較的方法來了解當操作符丟失的時候Python是如何撤回的。這個類允許我們執(zhí)行<比較。有趣的是,Python還可以通過切換參數(shù)順序來使用>比較。換句話說,x < y ≡ y > x。這是鏡像反射規(guī)則;我們將在第七章《創(chuàng)造數(shù)字》再次見到它。
我們將看到試圖評估不同的比較操作。創(chuàng)建兩個Cards類且比以不同的方式較它們,如下代碼片段所示:
>>> two = BlackJackCard_p(2, "?") >>> three = BlackJackCard_p(3, "?") >>> two < three Compare 2? < 3? True >>> two > three Compare 3? < 2? False >>> two == three False >>> two <= three Traceback (most recent call last): File "", line 1, in TypeError: unorderable types: BlackJackCard_p() <= BlackJackCard_p()
從這,我們可以看到two < three映射到two.__lt__(three)。
然而,對于two > three、沒有__gt__()方法定義;Python使用three.__lt__(two)作為一個后備計劃。
默認情況下,__eq__()方法是繼承自object;它比較對象ID;對象將有==和!=檢測,如下:
>>> two_c = BlackJackCard_p(2, "?") >>> two == two_c False
我們可以看到結(jié)果并不是我們所期待的。我們會經(jīng)常需要覆蓋默認的__eq__()實現(xiàn)。
同樣,操作符之間沒有邏輯聯(lián)系。在數(shù)學上,只要兩個就可以派生所有必要的比較。Python不會自動這樣做。相反,Python默認處理以下四對相反的檢測:
x < y ≡ y > x x ≤ y ≡ y ≥ x x = y ≡ y = x x =? y ≡ y =? x
這意味著我們必須至少從四對中提供一個。例如,我們可以提供__eq__()、__ne__()、__lt__()和__le__()。
@functools.total_ordering裝飾器克服了默認限制,并從__eq__()和__lt__()、__le__()、__gt__()中任意的一個,推導出其余的比較。我們將在第7章《創(chuàng)造數(shù)字》再次討論這個。
1. 設(shè)計比較在比較運算符有兩個顧慮:
顯而易見的問題是怎樣比較相同類的兩個對象
不太明顯的問題是怎樣比較不同類的對象
對于一個類有多個屬性,我們考慮比較運算符的時候經(jīng)常模棱兩可??赡懿皇呛芮宄覀円容^什么。
再次考慮不起眼的撲克牌。表達式如card1 == card2顯然是為了比較rank和suit。對嗎?或者總是為真?終究,suit在21點沒有意義。
如果我們想決定Hand對象是否可以分牌,我們最好看下這兩個代碼片段。以下是第一個代碼片段:
if hand.cards[0] == hand.cards[1]
下面是第二個代碼片段:
if hand.cards[0].rank == hand.cards[1].rank
雖然有一個是更短,簡潔并不總是最好的。如果我們定義相等只考慮rank,我們將很難定義單元測試,因為當一個單元測試應該關(guān)注完全正確的牌時,一個簡單的TestCase.assertEqual()方法會容忍各種各樣的牌。
表達式如card1 < = 7顯然是為了比較rank。
我們想要一些比較來比較牌的所有屬性,一些比較比較rank?我們該怎樣通過suit來排序?此外,相等性的比較必須并行計算hash。如果我們在hash中包含多個屬性,我們需要將其包含在相等性的比較中。在這種情況下,似乎牌之間的相等和不相等必須全部的Card比較,因為我們的hash包括了rank和suit。
Card之間的比較順序,無論如何都應該只有rank。和整數(shù)的比較同樣也應該只有rank。當發(fā)現(xiàn)分牌這一特殊情況,hand.cards[0].rank == hand.cards[1].rank會處理的很好,因為在分牌規(guī)則中它是顯式的。
2. 同一個類的對象的比較實現(xiàn)我們來看看一個簡單的同一類的比較通過觀察一個更完整的BlackJackCard類
class BlackJackCard: def __init__(self, rank, suit, hard, soft): self.rank = rank self.suit = suit self.hard = hard self.soft = soft def __lt__(self, other): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank < other.rank def __le__(self, other): try: return self.rank <= other.rank except AttributeError: return NotImplemented def __gt__(self, other): if not isinstance(other, BlackJackCard): return NotImplemented return self.rank > other.rank def __ge__(self, other): if not isinstance(other, BlackJackCard): return NotImplemented return self.rank >= other.rank def __eq__(self, other): if not isinstance(other, BlackJackCard): return NotImplemented return self.rank == other.rank and self.suit == other.suit def __ne__(self, other): if not isinstance(other, BlackJackCard): return NotImplemented return self.rank != other.rank and self.suit != other.suit def __str__(self): return "{rank}{suit}".format(**self.__dict__)
現(xiàn)在我們已經(jīng)定義了所有六個比較運算符。
我們已經(jīng)向您展示了兩種類型檢查:顯式和隱式。顯式類型檢查使用isinstance()。隱式類型檢查使用try:塊。使用try:塊有概念上的小優(yōu)勢,它能避免重復的類名。有可能會有人想發(fā)明一種變體牌來兼容BlackJackCard但不是定義為適當?shù)淖宇?。使?b>isinstance()可以防止一個無效類來保證工作正常。
try:塊會允許一個類使用rank屬性。這變成一個難以解決問題的風險會變?yōu)榱悖鳛轭愒趹贸绦虻钠渌胤娇赡軙?。同樣?b>Card實例與金融建模應用程序類比較出現(xiàn)根據(jù)牌值排序的屬性。
在接下來的例子中,我們將關(guān)注try:塊。isinstance()方法檢查一直是Python慣用方法且應用廣泛。我們通過顯式地返回NotImplemented來告知Python,這個操作符并不是用來實現(xiàn)這種類型數(shù)據(jù)的。Python可以顛倒參數(shù)順序來看看另一個操作數(shù)是否提供了實現(xiàn)方法。如果沒有找到有效的操作符,則TypeError異常將被拋出。
我們省略了三個子類定義和工廠函數(shù),留下card21()作為一個練習。
我們也省略了同類的比較,我們將在下一節(jié)看到。通過這個類,我們可以成功的比較牌。下面是一個例子,我們創(chuàng)建并比較三張牌:
>>> two = card21(2, "?") >>> three = card21(3, "?") >>> two_c = card21(2, "?")
根據(jù)這些Cards類,我們可以進行一些比較,如下代碼片段所示:
>>> two == two_c False >>> two.rank == two_c.rank True >>> two < three True >>> two_c < three True
定義似乎和預期的一樣。
3. 混合類對象的比較實現(xiàn)我們使用BlackJackCard類為例,當我們嘗試比較來自不同類的兩個操作數(shù)時,看看會發(fā)生什么。
下面是Card實例,我們可以和int值相比較:
>>> two = card21(2, "?") >>> two < 2 Traceback (most recent call last): File "", line 1, in TypeError: unorderable types: Number21Card() < int() >>> two > 2 Traceback (most recent call last): File " ", line 1, in TypeError: unorderable types: Number21Card() > int()
這就是我們期望的,BlackJackCard、Number21Card的子類沒有提供所需的特殊方法,所以有TypeError異常。
然而,考慮下面的兩個例子:
>>> two == 2 False >>> two == 3 False
為什么是這樣的結(jié)果?當面臨一個NotImplemented值,Python會對調(diào)操作數(shù)。在這種情況下,整數(shù)值定義int.__eq__()方法,容忍一個意想不到的類對象。
4. Hard點數(shù)、Soft點數(shù)和多態(tài)性我們定義Hand這樣它將執(zhí)行一些有意義的混合類比較。與其他的比較,我們必須確定這正是我們要比較的。
對于Hands之間的相等比較,我們應該比較所有牌。
對于Hands比較順序,我們需要比較每個Hand對象的一個屬性。為了逐個和int相比較,我們應該讓Hand對象的總數(shù)逐個相比較。為了有一個總數(shù),在21點游戲中我們必須分類hard點數(shù)和soft點數(shù)。
當有一個A在手,會有下面兩個候選點數(shù):
soft點數(shù)把A當作11。如果soft點數(shù)超過21,那么A當作1。
hard點數(shù)把A當作1。
這意味著手牌的總和不是簡單的牌的總和。
首先我們必須確定是否有一個A在手。確定這些,我們可以確定是否有一個有效的(小于或等于21)的soft點數(shù)。否則,我們將依靠hard點數(shù)。
多態(tài)性的一個癥狀是依靠isinstance()來確定子類的成員。一般來說,這違反了基本的封裝特性。一組好的多態(tài)的子類定義應該完全對等且?guī)в邢嗤姆椒ê灻?。理想情況下,類的定義是不透明的,我們不需要看類的內(nèi)部定義。一組多態(tài)的類使用廣泛的isinstance()檢測。在某些情況下,isinstance()是必要的。當使用一個內(nèi)置類的時候都會出現(xiàn)這樣。我們不能追溯添加方法函數(shù)到內(nèi)置類中,子類化它們來添加一個多態(tài)性輔助方法可能是不值得的。
對于一些特殊的方法,有必要看到isinstance()用于實現(xiàn)跨多個類對象的操作,沒有簡單的繼承層次結(jié)構(gòu)。在下一節(jié),與之無關(guān)的類中,我們將向您展示isinstance()的慣用方法。
對于我們牌的類層次結(jié)構(gòu),我們想要一個方法(或?qū)傩?來標識A,而不是用isinstance()。這是一個多態(tài)輔助方法。它確保我們可以辨別否則等價類會分開。
我們有兩個選擇:
添加一個類級別的屬性
添加一個方法
因為保守的賭注方式,我們有兩個原因去檢查A。如果莊家的牌是A,它會觸發(fā)一個保險的賭注。如果莊家手牌(或玩家的手牌)有一個A,會有一個soft點數(shù)與hard點數(shù)的計算。
hard點數(shù)和soft點數(shù)是card.soft - card.hard值的差。我們可以在AceCard里面的定義看到這個值是10。然而,深入類的內(nèi)部可以看到該實現(xiàn)違背了封裝性。
我們可以將BlackjackCard作為透明的,然后檢查card.soft - card.hard != 0是否為真。如果為真,這些信息足夠算出hard點數(shù)和soft點數(shù)。
下面是一個使用total方法計算soft值和hard值之間差值的版本:
def total(self): delta_soft = max(c.soft-c.hard for c in self.cards) hard = sum(c.hard for c in self.cards) if hard+delta_soft <= 21: return hard+delta_soft return hard
我們將計算hard點數(shù)和soft點數(shù)差作為delta_soft。對于大多數(shù)牌,差異是零。對于A,差異是非零。
鑒于hard點數(shù)和delta_soft,我們可以確定返回那個總數(shù)。如果是hard + delta_soft小于或等于21,值是soft點數(shù)。如果soft點數(shù)大于21,又恢復到hard點數(shù)。
我們可以考慮讓值21為顯式常量。一個有意義的名字有時比文字更有幫助。因為21點的規(guī)則,21不太可能會改變到一個不同的值。沒有比文字含義的21更有意義的了。
5. 混合類比較示例為Hand對象給定一個點數(shù),我們可以有意義地定義Hand實例之間的比較以及Hand和int之間的對比。為了確定我們做哪一種比較,我們被迫使用isinstance()。
以下是部分Hand比較的定義:
class Hand: def __init__(self, dealer_card, *cards): self.dealer_card = dealer_card self.cards = list(cards) def __str__(self): return ", ".join(map(str, self.cards)) def __repr__(self): return "{__class__.__name__}({dealer_card!r}, {_cards_str})" .format(__class__=self.__class__, _cards_str=", " .join(map(repr, self.cards)), **self.__dict__) def __eq__(self, other): if isinstance(other, int): return self.total() == other try: return (self.cards == other.cards and self.dealer_card == other.dealer_card) except AttributeError: return NotImplemented def __lt__(self, other): if isinstance(other, int): return self.total() < other try: return self.total() < other.total() except AttributeError: return NotImplemented def __le__(self, other): if isinstance(other, int): return self.total() <= other try: return self.total() <= other.total() except AttributeError: return NotImplemented __hash__ = None def total(self): delta_soft = max(c.soft-c.hard for c in self.cards) hard = sum(c.hard for c in self.cards) if hard+delta_soft <= 21: return hard+delta_soft return hard
我們定義了三個比較,不是所有的六個。
為了與Hand交互我,們需要幾個Card對象:
>>> two = card21(2, "?") >>> three = card21(3, "?") >>> two_c = card21(2, "?") >>> ace = card21(1, "?") >>> cards = [ace, two, two_c, three]
我們將使用這個序列的牌來看看兩個不同的hand實例。
第一個Hands對象有一個無關(guān)緊要的莊家的Card對象和先前創(chuàng)建的四張牌。Card對象中的一個是A:
>>> h= Hand(card21(10,"?"), *cards) >>> print(h) A?, 2?, 2?, 3? >>> h.total() 18
soft點數(shù)是18,hard點數(shù)是8。
下面是第二個對象,有一個額外的Card對象:
>>> h2= Hand(card21(10,"?"), card21(5,"?"), *cards) >>> print(h2) 5?, A?, 2?, 2?, 3? >>> h2.total() 13
hard點數(shù)是13。沒有soft點數(shù),因為它超過了21。
Hands之間的比較工作得很好,如下代碼片段所示:
>>> h < h2 False >>> h > h2 True
我們基于比較運算符對Hands進行排名。
我們也可以讓Hands與整數(shù)進行比較,如下代碼片段所示:
>>> h == 18 True >>> h < 19 True >>> h > 17 Traceback (most recent call last): File "", line 1, in TypeError: unorderable types: Hand() > int()
只要與整數(shù)的比較正常工作,Python不會被迫撤回。前面的例子告訴我們當沒有__gt__()方法。Python檢查相反的操作數(shù),對于Hand整數(shù)17也沒有適當?shù)?b>__lt__()方法。
我們可以添加必要的__gt__()和__ge__()函數(shù)使得Hand與整數(shù)正常工作。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/44187.html
摘要:這些基本的特殊方法在類中定義中幾乎總是需要的。和方法對于一個對象,有兩種字符串表示方法。這些都和內(nèi)置函數(shù)以及方法緊密結(jié)合。帶有說明符的合理響應是返回。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python 有許多特殊方法允許類與Python緊密結(jié)合,標準庫參考將其稱之為基本,基礎(chǔ)或本質(zhì)可能是更好的術(shù)語。這些特殊...
摘要:有三個用例通過和方法定義相等性檢測和值不可變對象對于有些無狀態(tài)對象,例如這些不能被更新的類型。請注意,我們將為不可變對象定義以上兩個。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __hash__() 方法 內(nèi)置hash()函數(shù)會調(diào)用給定對象的__hash__()方法。這里hash就是將(可能是復雜的)值縮減...
摘要:當引用計數(shù)為零,則不再需要該對象且可以銷毀。這表明當變量被刪除時引用計數(shù)正確的變?yōu)榱?。方法只能在循環(huán)被打破后且引用計數(shù)已經(jīng)為零時調(diào)用。這兩步的過程允許引用計數(shù)或垃圾收集刪除已引用的對象,讓弱引用懸空。這允許在方法設(shè)置對象屬性值之前進行處理。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __del__()方法 ...
摘要:第二章與的無縫集成基本特殊方法筆記中有有一些特殊的方法它們允許我們的類和更好的集成和方法通常方法表示的對象對用戶更加友好這個方法是有對象的方法實現(xiàn)的什么時候重寫跟非集合對象一個不包括其他集合對象的簡單對象這類對象格式通常不會特別復 第二章 與Python的無縫集成----基本特殊方法.(Mastering Objecting-oriented Python 筆記) python中有有一...
閱讀 891·2023-04-25 19:17
閱讀 2195·2021-09-10 11:26
閱讀 1908·2019-08-30 15:54
閱讀 3429·2019-08-30 15:53
閱讀 2688·2019-08-30 11:20
閱讀 3404·2019-08-29 15:12
閱讀 1238·2019-08-29 13:16
閱讀 2395·2019-08-26 12:19