背景
我有一个list
。 这个list
有许多对象。 每个对象都有一个id
。 现在的对象是不同的类型。
objects = [Aobject, Bobject, Cobject]
哪里
>>> Aobject != Bobject
True
>>> Aobject.id == Bobject.id
True
问题
我希望有一个list
基于独特的对象object.id
。
事情是这样的:
set(objects, key=operator.attrgetter('id'))
(这是行不通的。但是,我想是这样的)
seen = set()
# never use list as a variable name
[seen.add(obj.id) or obj for obj in mylist if obj.id not in seen]
这工作,因为set.add
回报None
,所以在list解析表达式总是产生obj
,但只有当obj.id
尚未加入seen
。
(表达式只能评估为None
如果obj is None
;在这种情况下, obj.id
。将产生一个异常万一mylist
包含None
值,改变测试到if obj and (obj.id not in seen)
)
请注意,这会给你一个具有给定的ID列表中的第一个对象。 @作者Abhijit的回答会给你最后一个这样的对象。
更新:
或者,ordereddict可能是一个不错的选择:
import collections
seen = collections.OrderedDict()
for obj in mylist:
# eliminate this check if you want the last item
if obj.id not in seen:
seen[obj.id] = obj
list(seen.values())
如何使用dict
(因为它的键是唯一的)?
假设我们有
class Object:
def __init__(self, id):
self.id = id
Aobject = Object(1)
Bobject = Object(1)
Cobject = Object(2)
objects = [Aobject, Bobject, Cobject]
然后list
与Object
S按唯一id
可以使用产生的场dict
在Python 3理解
unique_objects = list({object_.id: object_ for object_ in objects}.values())
在Python 2.7
unique_objects = {object_.id: object_ for object_ in objects}.values()
和在Python <2.7
unique_objects = dict([(object_.id, object_) for object_ in objects]).values()
最后,我们可以写函数(Python 3的版本)
def unique(elements, key):
return list({key(element): element for element in elements}.values())
其中elements
可以是任何iterable
和key
是一些callable
返回hashable
从对象elements
( key
quals到operator.attrgetter('id')
在我们的特定情况下)。
马辛的答案工作正常,但不看Python的我,因为列表解析变异seen
来自外部范围的对象,也有背后使用一些魔法set.add
方法和比较其结果(这是None
)与obj
。
和最后但并非不重要的部分:
基准
setup = '''
import random
class Object:
def __init__(self, id):
self.id = id
objects = [Object(random.randint(-100, 100))
for i in range(1000)]
'''
solution = '''
seen = set()
result = [seen.add(object_.id) or object_
for object_ in objects
if object_.id not in seen]
'''
print('list comprehension + set: ',
min(timeit.Timer(solution, setup).repeat(7, 1000)))
solution = '''
result = list({object_.id: object_
for object_ in objects}.values())
'''
print('dict comprehension: ',
min(timeit.Timer(solution, setup).repeat(7, 1000)))
在我的机器可以让
list comprehension + set: 0.20700953400228173
dict comprehension: 0.1477799109998159
鉴于你的对象的列表somelist
是这样的
[(Object [A] [1]), (Object [B] [1]), (Object [C] [2]), (Object [D] [2]), (Object [E] [3])]
你可以做这样的事情
>>> {e.id:e for e in somelist}.values()
[(Object [B] [1]), (Object [D] [2]), (Object [E] [3])]
如果你可以改变类的对象,你可以添加它们在集合比较中使用适当的方法:
# Assumption: this is the 'original' object
class OriginalExampleObject(object):
def __init__(self, name, nid):
self.name = name
self.id = nid
def __repr__(self):
return "(OriginalExampleObject [%s] [%s])" % (self.name, self.id)
class SetExampleObj(OriginalExampleObject):
def __init__(self, name, nid):
super(SetExampleObj, self).__init__(name, nid)
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return self.id.__hash__()
AObject = SetExampleObj("A", 1)
BObject = SetExampleObj("B", 1)
CObject = SetExampleObj("C", 2)
s = set()
s.add(AObject)
s.add(CObject)
print(s)
s.add(BObject)
print(s)
输出:
set([(OriginalExampleObject [A] [1]), (OriginalExampleObject [C] [2])])
set([(OriginalExampleObject [A] [1]), (OriginalExampleObject [C] [2])])
您可以使用unique_everseen
中的可用配方itertools
文档 。 这也是第三方库,例如,可用toolz.unique
。 注意:此方法将保持一个对象的第一个实例给定属性。
from toolz import unique
from operator import attrgetter
res = list(unique(objects, key=attrgetter('id')))
如果一个懒惰的迭代器是足够的,你可以忽略list
的转换。
一个相当简单的方式做这将是
for obj in mylist:
if obj.id not in s:
s.add(obj.id)
这应该添加没有看到任何标识。 拍摄时间是在源列表的大小呈线性关系。