簡體   English   中英

確保對象列表的大多數pythonic方法僅包含唯一項

[英]Most pythonic way of ensuring a list of objects contains only unique items

我有一個對象列表(Foo)。 Foo對象有幾個屬性。 Foo對象的實例等效(等於)Foo對象的另一個實例iff(當且僅當)所有屬性相等。

我有以下代碼:

class Foo(object):
    def __init__(self, myid):
        self.myid=myid

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            print 'DEBUG: self:',self.__dict__ 
            print 'DEBUG: other:',other.__dict__ 
            return self.__dict__ == other.__dict__
        else:
            print 'DEBUG: ATTEMPT TO COMPARE DIFFERENT CLASSES:',self.__class__,'compared to:', other.__class__
            return False    


import copy

f1 = Foo(1)
f2 = Foo(2)
f3 = Foo(3)
f4 = Foo(4)
f5 = copy.deepcopy(f3) # overkill here (I know), but needed for my real code

f_list = [f1,f2,f3,f4,f5]

# Surely, there must be a better way? (this dosen't work BTW!)
new_foo_list = list(set(f_list))

在處理簡單類型(int,float,string - 和令人驚訝的datetime.datetime類型)時,我經常使用上面的這個小(反?)'模式'(轉換為set和back),但是它已經成為了一個更多的剪切器涉及數據類型 - 如上面的Foo。

那么,我怎樣才能將上面的列表f1更改為一個唯一項列表 - 無需遍歷每個項目並檢查它是否已存在於某些臨時緩存等中?

什么是最pythonic的方式來做到這一點?

首先,我想強調的是,使用set肯定不是反模式。 set s消除O(n)時間內的重復,這是你能做的最好的,並且比將每個項目與每個其他項目進行比較的天真O(n ^ 2)解決方案更好。 它甚至比排序更好 - 事實上,似乎你的數據結構甚至可能沒有自然順序,在這種情況下,排序沒有多大意義。

在這種情況下使用集合的問題是您必須定義自定義__hash__方法。 其他人說過這個。 但是,你是否可以輕易地這樣做是一個懸而未決的問題 - 這取決於你沒有告訴我們的實際課程的細節。 例如,如果上面的Foo對象的任何屬性都不可清除,那么創建自定義哈希函數將很困難,因為您不僅要為Foo對象編寫自定義哈希,還必須編寫每種其他類型的對象的自定義哈希!

因此,如果您想要一個確定的答案,您需要告訴我們更多關於您的課程具有哪些屬性的信息。 但我可以提出一些猜測。

假設 可以Foo對象編寫散列函數,但也假設Foo對象是可變的,因此實際上不應該__hash__方法,正如Niklas B.指出的那樣,這是一種可行的方法。 創建一個功能freeze ,給定的可變實例Foo ,返回數據的不可變集合Foo 例如,假設Foo有一個dict和一個list ; freeze返回一個tuple其中包含tuple tuple s(表示dict )和另一個tuple (表示list )的tuple 函數freeze應具有以下屬性:

freeze(a) == freeze(b)

當且僅當

a == b

現在通過以下代碼傳遞您的列表:

dupe_free = dict((freeze(x), x) for x in dupe_list).values()

現在你有一個O(n)時間的欺騙免費列表。 (確實,在添加這個建議之后,我看到fraxel提出類似的東西;但我認為使用自定義函數 - 甚至是方法 - (x.freeze(), x) - 是更好的方法,而不是而不是像他一樣依賴__dict__ ,這可能是不可靠的。對於你的自定義__eq__方法也一樣,IMO - __dict__並不總是一個安全的快捷方式,因為我無法進入這里。)

另一種方法是首先只使用不可變對象! 例如,您可以使用namedtuple 這是從python文檔中竊取的一個例子:

>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)

你嘗試過使用set (或frozenset )嗎? 它明確地用於保存一組唯一的項目。

但是,您需要創建一個合適的__hash__方法。 set (和frozenset )使用__hash__方法來散列對象; __eq__僅用於碰撞,AFAIK。 因此,您將需要使用hash(frozenset(self.__dict__.items()))

根據文檔 ,您需要為自定義類定義__hash__()__eq__()以使用setfrozenset正確工作,因為兩者都是使用CPython中的哈希表實現的。

如果你實現了__hash__ ,請記住,如果a == b ,那么hash(a)必須等於hash(b) 而不是比較整個__dict__ ,我建議你的簡單類的以下更直接的實現:

class Foo(object):
    def __init__(self, myid):
        self.myid = myid

    def __eq__(self, other):
        return isinstance(other, self.__class__) and other.myid == self.myid

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

如果您的對象包含可變屬性,則不應將其放在集合中或將其用作字典鍵。

這是一個替代方法,只需為實例創建一個由__dict__.items()鍵入的字典:

f_list = [f1,f2,f3,f4,f5]
f_dict = dict([(tuple(i.__dict__.items()), i) for i in f_list])
print f_dict
print f_dict.values()
#output:
{(('myid', 1),): <__main__.Foo object at 0xb75e190c>, 
 (('myid', 2),): <__main__.Foo object at 0xb75e184c>, 
 (('myid', 3),): <__main__.Foo object at 0xb75e1f6c>, 
 (('myid', 4),): <__main__.Foo object at 0xb75e1cec>}
[<__main__.Foo object at 0xb75e190c>, 
 <__main__.Foo object at 0xb75e184c>, 
 <__main__.Foo object at 0xb75e1f6c>, 
 <__main__.Foo object at 0xb75e1cec>]

這樣,您只需讓字典根據屬性處理唯一性,並可以通過獲取值輕松檢索對象。

如果您被允許,可以使用http://docs.python.org/library/sets.html

list = [1,2,3,3,45,4,45,6]
print set(list)
set([1, 2, 3, 4, 6, 45])
x = set(list)
print x
set([1, 2, 3, 4, 6, 45])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM