Blog Cover Image

Inspire you to have New thinking, Walk out your unique Road.

[技術小筆記] 利用eq, hash 解決去除重複物件(object)

Posted on Dec 10, 2018

恩... 我重新改寫一下這篇

因為在我發文的後幾天我解決了,且在前輩的幫助下我有了更新一層的領悟

我有點不知道這篇的知識該擺在哪,小筆記當初規劃是開來存放每次解決的小問題

今天介紹 __eq__, __hash__在 Python 物件 Object 的做法


起初是因為一連串重複的 object list 中取除獨特的物件 list

若 set() 使用在數值上,是可以直接去除重複值的

>>> list = [2,3,4,5,5,5,5,2,2,3,4,5]
>>> list
[2, 3, 4, 5, 5, 5, 5, 2, 2, 3, 4, 5]
>>> set(list)
{2, 3, 4, 5}

所以代表 set 利用在數字上是可以去除重複值

假設我的物件 List 長這樣

o_list = [data, data, data, data, data, data]

其中只會有 3 個物件是獨立的,但利用 set()去實作並沒有篩選出獨立的 3 個物件

我先後參考這些文件撰寫出程式碼

How does a Python set([]) check if two objects are equal? What methods does an object need to define to customise this?

How to remove duplicates in set for objects?

以及許多

然後造樣寫了一個這個

class RemoveDuplicate(object):

    def __init__(self, data):
        self.data = data.data
        self.id = data.id
        self.column = data.column_name
        self.table = data.table_name

    def __eq__(self, other):
        return (self.id) == (other.id)

    def __hash__(self):
        return hash(self.id)

data_set = set()
for data, book in library:
    for each_data in list(data):
        data_list.add(RemoveDuplicate(each_data))

最後比較折騰我的原因是因為我的程式實際上傳的 data 資料是 Object 裡又包 Object

而且剛好內部的 object 是unhashable

我的 data 簡單來說長這樣

data
    id = 1
    object(OrderedDict)
    column = "..."
    table = "..."

在我的 data 中有包了一個 object 是  OrderedDict,他是不可被 hash


來介紹一下此篇 Object, Equal, Hash

Equal 這個功能,前輩說在各大程式語言內都看得到,例如 JAVA  openhome.ccCodeData

例如C #

前輩丟了這篇給我讓我捉模一下  Python Hashes and Equality

Equal 在這寫是檢查兩個物件有沒有相同

而相同的依據就是 __eq__ 去定義

def __eq__(self, other):
    return (self.id) == (other.id)

在我的程式碼內是使用 data.id 若相同就是同個物件

什麼 Hash? 雜湊函式

講一個例子就是有一副樸克牌,Hash 的元件為紅心,黑桃,方塊及梅花

接下來塞 52 張不同花色的牌卡下去後,經過 Hash,樸克牌會像牌七一樣,同樣花色的排一排,該是紅心的牌他就會排在紅心的 List 後

紅心, [紅心Q, 紅心3, 紅心1, ...]

黑桃, [黑桃1, 黑桃K, 黑桃2,...]

Hash 算是將物件中的某幾個元件(自己定)建立為固定的指紋, 若資料符合一系列的指紋就會被識別出來

而且可被 Hash(Hashable)的元件要是不可變動(Immutable),這樣才能拿來作 eq 比較

# 我用id 當hash
def __hash__(self):
    return hash(self.id)

所以eq + hash ,就是 hash 會篩選出不同的桶子,而 eq 則會去比對說不同桶子內若其中有什麼元件是相同的,就會判定兩個桶子(物件)是一樣的

根據 Python2.7文章中,

要使用 Set 做 eq 比較,唯有被 hash 過的物件才會被放進 eq 比較

若 Class Object 沒有__eq__()  的話,那就不用__hash__()

可是如果有使用__eq__() ,就一定要用__hash__()

意思是寫了 eq, 就要用 hash,而 set 會自動呼叫__hash__()

來看一下 Python 3 與 2 的文件 set 3.6  及  set 2.7

set 內的元件必須要可被Hash
The elements of a set must be <span class="xref std std-term">hashable</span>.




set不能包含可變動的元件,像是list或字典。
但他可包含不可變動的群組,像是tuple
As a result, <strong>sets cannot contain mutable elements</strong> such as lists or dictionaries.
However, they <strong>can contain immutable collections</strong> such as tuples or instances of<code class="xref py py-class docutils literal notranslate"><span class="pre">ImmutableSet</span></code>.

甚麼是 Mutable/ Immutable?

我找了一篇文章

Mutable vs Immutable Objects in Python

裡面提及

要使用 Set 及 eqhash

首先確定 eq 的條件,接著

只有被 hash 的元件才會拿來做比較

set 會自動執行 hash, 若沒有 hash 函式就不會進到 eq

只有可被 hash(不可變動的元件 Immutable),才可做為 eq 做比對

至於如果遇到字典, 串列,或者像我的例子中 OrderedDict',真的要對這些變動元件做 hash 怎麼辦呢?

在我的 data 物件中,包含了OrderedDict, 而OrderedDict,屬於 Dictionary 的一種

Dictionary 是  Mutable objects, 不可被hash

Python unhashable type: 'OrderedDict'

In your case, var1 contains some object that is not hashable (it does not implement hash()). This object is an OrderedDict, which is a mutable object and is not hashable by design.

對於 unhashable 的物件,你可使用

frozenset() 回傳

以下是我的程式碼

class RemoveDuplicate(object):
    def __init__(self, data):
        <strong>self.data = data.data</strong>
        self.id = data.id
        self.column = data.column_name
        self.table = data.table_name

    def __eq__(self, other):
        return (self.id) == (other.id)

    def __hash__(self):
        return hash((self.id, <strong>frozenset(self.data)</strong>, self.table, self.column))

data_set = set()
for data, book in library:
    for each_data in list(data):
        data_list.add(RemoveDuplicate(each_data))

粗字是 OrderedDict 物件,可以使用這樣的方法來 Hash

參考文章, 此篇可以解決 dict unhashable