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

資訊專欄INFORMATION COLUMN

[譯] 與 Python 無(wú)縫集成——基本特殊方法 2

hzc / 2606人閱讀

摘要:有三個(gè)用例通過(guò)和方法定義相等性檢測(cè)和值不可變對(duì)象對(duì)于有些無(wú)狀態(tài)對(duì)象,例如這些不能被更新的類型。請(qǐng)注意,我們將為不可變對(duì)象定義以上兩個(gè)。

注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python

__hash__() 方法

內(nèi)置hash()函數(shù)會(huì)調(diào)用給定對(duì)象的__hash__()方法。這里hash就是將(可能是復(fù)雜的)值縮減為小整數(shù)值的計(jì)算。理想情況下,一個(gè)hash值反映了源值的所有信息。還有一些hash計(jì)算經(jīng)常用于加密,生成非常大的值。

Python包含兩個(gè)hash庫(kù)。在hashlib模塊中有高品質(zhì)加密hash函數(shù)。zlib模塊有兩個(gè)高速hash函數(shù):adler32()crc32()。對(duì)于相對(duì)簡(jiǎn)單的值,我們不使用這些。而對(duì)于大型、復(fù)雜的值,使用這些算法會(huì)有很大幫助。

hash()函數(shù)(和相關(guān)的__hash__()方法)用于創(chuàng)建集合中使用的小整數(shù)key,如下集合:setfrozensetdict。這些集合使用不可變對(duì)象的hash值來(lái)快速定位對(duì)象。

在這里不變性是很重要的,我們會(huì)多次提到它。不可變對(duì)象不會(huì)改變它們的狀態(tài)。例如,數(shù)字3并沒(méi)有改變狀態(tài),它總是3。更復(fù)雜的對(duì)象也是一樣的,可以有一個(gè)不變的狀態(tài)。Python字符串是不可變的,這樣它們可以用來(lái)映射作集合的key。

默認(rèn)的__hash__()繼承自對(duì)象本身,返回一個(gè)基于對(duì)象的內(nèi)部ID值。這個(gè)值可以通過(guò)id()函數(shù)看到,如下:

>>> x = object()
>>> hash(x)
269741571
>>> id(x)
4315865136
>>> id(x) / 16
269741571.0

由此,我們可以看到在作者的系統(tǒng)中,hash值就是對(duì)象的id / 16。這一細(xì)節(jié)針對(duì)不同平臺(tái)可能會(huì)有所不同。例如,CPython使用可移植的C庫(kù),Jython依賴于Java JVM。

至關(guān)重要的是,內(nèi)部ID和默認(rèn)__hash__()方法間有一種強(qiáng)聯(lián)系。這意味著每個(gè)對(duì)象默認(rèn)是可以hash且完全不同的,即使它們似乎相同。

如果我們想將有相同值的不同對(duì)象合并到單個(gè)可hash對(duì)象中,我們需要修改這個(gè)。在下一節(jié)中,我們將看一個(gè)示例,該示例一個(gè)卡片的兩個(gè)實(shí)例被視為是同一個(gè)對(duì)象。

1. 判斷什么需要hash

不是每一個(gè)對(duì)象都需要提供一個(gè)hash值。具體地說(shuō),如果我們創(chuàng)建一個(gè)有狀態(tài)、可變對(duì)象的類,該類萬(wàn)萬(wàn)不能返回hash值。__hash__應(yīng)該定義為None。

另一方面,不可變對(duì)象返回一個(gè)hash值,這樣對(duì)象就可用作字典中的key或集合中的一員。在這種情況下,hash值需要用并行的方式檢測(cè)相等性。對(duì)象有不同的hash值但被看作相等的對(duì)象是糟糕的。相反的,對(duì)象具有相同hash值,實(shí)際上不相等是可以接受的。

我們?cè)诒容^運(yùn)算符中看到的__eq__()方法與hash關(guān)系密切。

有三種級(jí)別的等式比較:

相同的hash值:這意味著兩個(gè)對(duì)象可能是相等的。該hash值為我們提供了一個(gè)快速檢查對(duì)象相等的可能性。如果hash值是不同的,兩個(gè)對(duì)象不可能是相等的,他們也不可能是相同的對(duì)象。

等號(hào)比較:這意味著hash值也一定相等。這是==操作符的定義。對(duì)象可能是相同的對(duì)象。

相同的IDD:這意味著他們是同一個(gè)對(duì)象。進(jìn)行了等號(hào)比較且有相同的hash值。這是is操作符的定義。

Hash的基本規(guī)律(FLH)是:對(duì)象等號(hào)比較必須具有相同的hash值。

在相等性檢測(cè)中我們能想到的第一步是hash比較。

然而,反過(guò)來(lái)是不正確的。對(duì)象可以有相同的hash值但比較是不相等的。在創(chuàng)建集合或字典時(shí)導(dǎo)致一些預(yù)計(jì)的處理開銷是正當(dāng)?shù)?。我們不能確切的從更大的數(shù)據(jù)結(jié)構(gòu)創(chuàng)建不同的64位hash值。將有不相等的對(duì)象被簡(jiǎn)化為一致相等的hash值。

在使用集合和字典時(shí)比較hash值是一個(gè)預(yù)期的開銷,它們是同時(shí)發(fā)生的。這些集合有內(nèi)部的算法在hash沖突時(shí)會(huì)使用替換位置進(jìn)行處理。

有三個(gè)用例通過(guò)__eq__()__hash__()方法定義相等性檢測(cè)和hash值:

不可變對(duì)象:對(duì)于有些無(wú)狀態(tài)對(duì)象,例如tuples、namedtuples、frozensets這些不能被更新的類型。我們有兩個(gè)選擇:

不定義__hash__()__eq__()。這意味著什么都不做,使用繼承的定義。在這種情況下__hash__()返回一個(gè)簡(jiǎn)單的函數(shù)對(duì)象的ID值,然后__eq__()比較ID值。默認(rèn)的相等性檢測(cè)有時(shí)是違反直覺(jué)的。我們的應(yīng)用程序可能需要兩個(gè)Card(1, Clubs)實(shí)例檢測(cè)相等性和計(jì)算相同的hash,默認(rèn)情況下是不會(huì)發(fā)生這種情況的。

定義__hash__()__eq__()。請(qǐng)注意,我們將為不可變對(duì)象定義以上兩個(gè)。

可變對(duì)象:這些是有狀態(tài)的對(duì)象,可以進(jìn)行內(nèi)部修改。我們有一個(gè)選擇:

定義__eq__(),但__hash__()設(shè)置為None。這些不能被用作dict中的key或set中的項(xiàng)目。

請(qǐng)注意,有一個(gè)額外可能的組合:定義__hash__()但對(duì)__eq__()使用一個(gè)默認(rèn)的定義。這其實(shí)是浪費(fèi)時(shí)間,作為默認(rèn)的__eq__()方法其實(shí)和is操作符是一樣的。默認(rèn)的__hash__()方法會(huì)為相同的行為編寫更少的代碼。

我們可以詳細(xì)的看看這三種情況。

2. 為不可變對(duì)象繼承定義

讓我們看看默認(rèn)定義操作。下面是一個(gè)簡(jiǎn)單的類層次結(jié)構(gòu),使用默認(rèn)的__hash__()__eq__()定義:

class Card:

    insure= False

    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name__}(suit={suit!r}, rank={rank!r})"
          .format(__class__=self.__class__, **self.__dict__)

    def __str__(self):
       return "{rank}{suit}".format(**self.__dict__)

class NumberCard(Card):

    def __init__(self, rank, suit):
        super().__init__(str(rank), suit, rank, rank)

class AceCard(Card):

    def __init__(self, rank, suit):
        super().__init__("A", suit, 1, 11)

class FaceCard(Card):

    def __init__(self, rank, suit):
        super().__init__({11: "J", 12: "Q", 13: "K"}[rank], suit, 10, 10)

這是一個(gè)不可變對(duì)象的類層次結(jié)構(gòu)。我們還沒(méi)有實(shí)現(xiàn)特殊方法防止屬性更新。在下一章我們將看看屬性訪問(wèn)。

當(dāng)我們使用這個(gè)類層次結(jié)構(gòu)時(shí),看看會(huì)發(fā)生什么:

>>> c1 = AceCard(1, "?")
>>> c2 = AceCard(1, "?")

我們定義的兩個(gè)相同的Card實(shí)例。我們可以檢查id()的值,如下代碼片段所示:

>>> print(id(c1), id(c2))
4302577232 4302576976

他們有不同的id()號(hào),不同的對(duì)象。這符合我們的預(yù)期。

我們可以使用is操作符來(lái)檢查它們是否一樣,如下代碼片段所示:

>>> c1 is c2
False

is測(cè)試”是基于id()的數(shù)字,它告訴我們,它們確實(shí)是獨(dú)立的對(duì)象。

我們可以看到,它們的hash值是不同的:

>>> print(hash(c1), hash(c2))
268911077 268911061

這些hash值直接來(lái)自id()值。這是我們期望繼承的方法。在這個(gè)實(shí)現(xiàn)中,我們可以從id()函數(shù)中計(jì)算出hash值,如下代碼片段所示:

>>> id(c1) / 16
268911077.0
>>> id(c2) / 16
268911061.0

hash值是不同的,它們之間的比較必須不相等。這符合hash的定義和相等性定義。然而,這違背了我們對(duì)這個(gè)類的期望。下面是一個(gè)相等性檢查:

>>> print(c1 == c2)
False

我們使用相同的參數(shù)創(chuàng)建了它們。它們比較后不相等。在某些應(yīng)用程序中,這樣不好。例如,當(dāng)處理牌的時(shí)候累加計(jì)數(shù),我們不想給一張牌做6個(gè)計(jì)數(shù)因?yàn)槭褂玫氖?副牌牌盒。

我們可以看到,他們是不可變對(duì)象,我們可以把它們放在一個(gè)集合里:

>>> print(set([c1, c2]))
{AceCard(suit="?", rank=1), AceCard(suit="?", rank=1)}

這是標(biāo)準(zhǔn)庫(kù)參考文檔中記錄的行為。默認(rèn)情況下,我們會(huì)得到一個(gè)基于對(duì)象ID的__hash__()方法,這樣每個(gè)實(shí)例都唯一出現(xiàn)。然而,這并不總是我們想要的。

3. 覆寫不可變對(duì)象的定義

下面是一個(gè)簡(jiǎn)單的類層次結(jié)構(gòu),它為我們提供了__hash__()__eq__()的定義:

class Card2:

    insure = False

    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".
          format(__class__=self.__class__, **self.__dict__)

    def __str__(self):
        return "{rank}{suit}".format(**self.__dict__)

    def __eq__(self, other):
        return self.suit == other.suit and self.rank == other.rank

    def __hash__(self):
        return hash(self.suit) ^ hash(self.rank)

class AceCard2(Card2):

    insure = True

    def __init__(self, rank, suit):
        super().__init__("A", suit, 1, 11)

原則上這個(gè)對(duì)象是不可變的。還沒(méi)有正式的機(jī)制來(lái)讓它不可變。關(guān)于這個(gè)機(jī)制我們將在第3章《屬性訪問(wèn)、屬性和描述符》中看看如何防止屬性值變化。

同時(shí),注意前面的代碼省略了的兩個(gè)子類,從前面的示例來(lái)看并沒(méi)有顯著的改變。

__eq__()方法函數(shù)比較這兩個(gè)基本值:suitrank。它不比較派生自rankhard值和soft值。

21點(diǎn)的規(guī)則使這個(gè)定義有點(diǎn)可疑?;ㄉ?1點(diǎn)中實(shí)際上并不重要。我們只是比較牌值嗎?我們是否應(yīng)該定義一個(gè)額外的方法,而不是僅僅比較牌值?或者,我們應(yīng)該依靠應(yīng)用程序比較牌值的正確性?對(duì)于這些問(wèn)題沒(méi)有最好的回答,只是做好一個(gè)權(quán)衡。

__hash__()方法函數(shù)計(jì)算的位模式使用兩個(gè)值作為基礎(chǔ)進(jìn)行hash,然后對(duì)hash值進(jìn)行異或計(jì)算。使用^操作符是一種應(yīng)急的hash方法,很有用。對(duì)于更大、更復(fù)雜的對(duì)象,使用更復(fù)雜的hash會(huì)更合適。在構(gòu)造某個(gè)東東之前使用ziplib會(huì)有bug哦。

讓我們來(lái)看看這些類對(duì)象的行為。我們期望它們比較是相等的且能夠在集合和字典中正常使用。這里有兩個(gè)對(duì)象:

>>> c1 = AceCard2(1, "?")
>>> c2 = AceCard2(1, "?")

我們定義的兩個(gè)實(shí)例似乎是相同的牌。我們可以檢查ID值,以確保他們是不同的對(duì)象:

>>> print(id(c1), id(c2))
4302577040 4302577296
>>> print(c1 is c2)
False

這些有不同的id()數(shù)字。當(dāng)我們通過(guò)is操作符檢測(cè),我們看到它們是截然不同的。

讓我們來(lái)比較一下hash值:

>>> print(hash(c1), hash(c2))
1259258073890 1259258073890

hash值是相同的。這意味著他們可能是相等的。

等號(hào)操作符告訴我們,他們是相等的

>>> print(c1 == c2)
True

它們是不可變的,我們可以把它們放到一個(gè)集合中,如下所示:

>>> print(set([c1, c2]))
{AceCard2(suit="?", rank="A")}

對(duì)于復(fù)雜的不可變對(duì)象是符合我們預(yù)期的。我們必須覆蓋這兩個(gè)特殊方法獲得一致的、有意義的結(jié)果。

4. 覆寫可變對(duì)象的定義

這個(gè)例子將繼續(xù)使用Cards類。可變的牌是很奇怪的想法,甚至是錯(cuò)誤的。然而,我們想小小調(diào)整一下前面的例子。

以下是一個(gè)類層次結(jié)構(gòu),為我們提供了適合可變對(duì)象的__hash__()__eq__()的定義:

class Card3:

    insure = False

    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".
          format(__class__=self.__class__, **self.__dict__)

    def __str__(self):
        return "{rank}{suit}".format(**self.__dict__)

    def __eq__(self, other):
        return self.suit == other.suit and self.rank == other.rank
        # and self.hard == other.hard and self.soft == other.soft

       __hash__ = None

class AceCard3(Card3):

    insure= True

    def __init__(self, rank, suit):
        super().__init__("A", suit, 1, 11)

讓我們來(lái)看看這些類對(duì)象的行為。我們期望它們比較是相等的,但是在集合和字典中完全不起作用。我們創(chuàng)建如下兩個(gè)對(duì)象:

>>> c1 = AceCard3(1, "?")
>>> c2 = AceCard3(1, "?")

我們定義的兩個(gè)實(shí)例似乎是相同的牌。我們可以檢查ID值,以確保他們是不同的對(duì)象:

>>> print(id(c1), id(c2))
4302577040 4302577296

如果我們嘗試獲取hash值,毫無(wú)意外,我們將會(huì)看到如下情形:

>>> print(hash(c1), hash(c2))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unhashable type: "AceCard3"

__hash__被設(shè)置為None,這些Card3對(duì)象不能被hash,不能為hash()函數(shù)提供值。和我們預(yù)期的是一樣的。

我們可以執(zhí)行相等性比較,如下代碼片段所示:

>>> print(c1 == c2)
True

相等性測(cè)試工作正常,才能很好的讓我們比較牌。它們只是不能被插入到集合或用作字典的key。

我們?cè)囋嚂?huì)發(fā)生什么:

>>> print(set([c1, c2]))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unhashable type: "AceCard3"

當(dāng)試圖把這些放到集合中,我們會(huì)得到這樣一個(gè)異常。

顯然,這不是一個(gè)正確的定義,在現(xiàn)實(shí)生活中和牌一樣是不可變對(duì)象。這種風(fēng)格的定義更適合有狀態(tài)的對(duì)象,如Hand,它的內(nèi)容總是在變化的。我們將通過(guò)第二個(gè)示例為您提供一個(gè)有狀態(tài)的對(duì)象在接下來(lái)的章節(jié)。

5. 從可變手牌變?yōu)閮鼋Y(jié)手牌

如果我們想對(duì)具體的Hand實(shí)例進(jìn)行統(tǒng)計(jì)分析,我們可能需要?jiǎng)?chuàng)建一個(gè)字典來(lái)映射Hand實(shí)例到計(jì)數(shù)中。我們不能用一個(gè)可變Hand類作為一個(gè)映射的key。然而,我們可以并行的設(shè)計(jì)setfrozenset并且創(chuàng)建兩個(gè)類:HandFrozenHand。這允許我們能通過(guò)FrozenHand類“凍結(jié)”Hand類;凍結(jié)版本是不可變的,可以作為一個(gè)字典的key。

下面是一個(gè)簡(jiǎn)單的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):
        return self.cards == other.cards and self.dealer_card == other.dealer_card

    __hash__ = None

這是一個(gè)可變對(duì)象(__hash__None),它有一個(gè)恰當(dāng)?shù)南嗟刃詸z測(cè)來(lái)比較兩副手牌。

下面是關(guān)于Hand的一個(gè)“凍結(jié)”版本:

import sys

class FrozenHand(Hand):

    def __init__(self, *args, **kw):
        if len(args) == 1 and isinstance(args[0], Hand):
            # Clone a hand
            other = args[0]
            self.dealer_card = other.dealer_card
            self.cards = other.cards
        else:
            # Build a fresh hand
            super().__init__(*args, **kw)

    def __hash__(self):
        h = 0
        for c in self.cards:
            h = (h + hash(c)) % sys.hash_info.modulus
        return h

凍結(jié)版本有一個(gè)構(gòu)造函數(shù),將從另一個(gè)Hand類構(gòu)建一個(gè)Hand類。它定義了一個(gè)__hash__()方法,計(jì)算牌的hash值的總和,這個(gè)值受sys.hash_info.modules限制。大多數(shù)情況,這種基于模塊的計(jì)算,在計(jì)算復(fù)合對(duì)象hash時(shí)效果相當(dāng)好。

我們現(xiàn)在可以使用這些類進(jìn)行操作,如下代碼片段所示:

stats = defaultdict(int)
d = Deck()
h = Hand(d.pop(), d.pop(), d.pop())
h_f = FrozenHand(h)
stats[h_f] += 1

我們需要初始化統(tǒng)計(jì)字典——statsdefaultdict字典,可以收集整型計(jì)數(shù)。為此我們可以使用一個(gè)collections.Counter對(duì)象。

通過(guò)凍結(jié)Hand類,我們可以把它作為一個(gè)字典的key,收集每副手牌計(jì)數(shù)的問(wèn)題就可以解決了。

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

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

相關(guān)文章

  • [] Python 無(wú)縫集成——基本特殊方法 1

    摘要:這些基本的特殊方法在類中定義中幾乎總是需要的。和方法對(duì)于一個(gè)對(duì)象,有兩種字符串表示方法。這些都和內(nèi)置函數(shù)以及方法緊密結(jié)合。帶有說(shuō)明符的合理響應(yīng)是返回。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python 有許多特殊方法允許類與Python緊密結(jié)合,標(biāo)準(zhǔn)庫(kù)參考將其稱之為基本,基礎(chǔ)或本質(zhì)可能是更好的術(shù)語(yǔ)。這些特殊...

    yzd 評(píng)論0 收藏0
  • [] Python 無(wú)縫集成——基本特殊方法 3

    摘要:比較運(yùn)算符方法有六個(gè)比較運(yùn)算符。根據(jù)文檔,其映射工作如下第七章創(chuàng)建數(shù)字我們會(huì)再次回到比較運(yùn)算符這塊。同一個(gè)類的對(duì)象的比較實(shí)現(xiàn)我們來(lái)看看一個(gè)簡(jiǎn)單的同一類的比較通過(guò)觀察一個(gè)更完整的類現(xiàn)在我們已經(jīng)定義了所有六個(gè)比較運(yùn)算符。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __bool__()方法 Python對(duì)假有個(gè)很...

    2json 評(píng)論0 收藏0
  • [] Python 無(wú)縫集成——基本特殊方法 4

    摘要:當(dāng)引用計(jì)數(shù)為零,則不再需要該對(duì)象且可以銷毀。這表明當(dāng)變量被刪除時(shí)引用計(jì)數(shù)正確的變?yōu)榱?。方法只能在循環(huán)被打破后且引用計(jì)數(shù)已經(jīng)為零時(shí)調(diào)用。這兩步的過(guò)程允許引用計(jì)數(shù)或垃圾收集刪除已引用的對(duì)象,讓弱引用懸空。這允許在方法設(shè)置對(duì)象屬性值之前進(jìn)行處理。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __del__()方法 ...

    Allen 評(píng)論0 收藏0
  • Python無(wú)縫集成----基本特殊方法.(Mastering Objecting-orient

    摘要:第二章與的無(wú)縫集成基本特殊方法筆記中有有一些特殊的方法它們?cè)试S我們的類和更好的集成和方法通常方法表示的對(duì)象對(duì)用戶更加友好這個(gè)方法是有對(duì)象的方法實(shí)現(xiàn)的什么時(shí)候重寫跟非集合對(duì)象一個(gè)不包括其他集合對(duì)象的簡(jiǎn)單對(duì)象這類對(duì)象格式通常不會(huì)特別復(fù) 第二章 與Python的無(wú)縫集成----基本特殊方法.(Mastering Objecting-oriented Python 筆記) python中有有一...

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

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

0條評(píng)論

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