[英]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__()
以使用set
或frozenset
正確工作,因為兩者都是使用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.