Как получить список объектов с уникальным атрибутом
Фон
У меня есть list
.
Этот list
имеет много объектов. Каждый объект имеет id
. Теперь объекты имеют разные типы.
objects = [Aobject, Bobject, Cobject]
где
>>> Aobject != Bobject
True
>>> Aobject.id == Bobject.id
True
Проблема
Я хочу list
уникальных объектов на основе object.id
.
Что-то вроде этого:
set(objects, key=operator.attrgetter('id'))
(Это не работает, но я хочу что-то вроде этого)
Ответы
Ответ 1
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
, поэтому выражение в понимании списка всегда дает obj
, но только если obj.id
еще не добавлено в seen
.
(Выражение может быть оценено как None
, если obj is None
; в этом случае obj.id
вызовет исключение. Если mylist
содержит значения None
, измените тест на if obj and (obj.id not in seen)
)
Обратите внимание, что это даст вам первый объект в списке, который имеет данный идентификатор. @Abhijit ответ даст вам последний такой объект.
Обновление:
В качестве альтернативы, приказ может быть хорошим выбором:
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())
Ответ 2
Как насчет использования 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
полем 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 & lt; 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
равен operator.attrgetter('id')
в нашем конкретном случае случай).
Ответ марцина отлично работает, но не выглядит для меня Pythonic, так как понимание списка изменяет объект seen
из внешней области видимости, также есть некоторая магия в использовании метода set.add
и сравнении его результата (то есть None
) с obj
.
И последняя, но не менее важная часть:
Benchmark
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
Ответ 3
С учетом вашего списка объектов 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])]
Ответ 4
Если вы можете изменить класс объектов, вы можете добавить соответствующие методы, которые используются в сравнении сравнения:
# 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])])
Ответ 5
Вы можете просто использовать установочное понимание (наиболее эффективное и простое) так просто, как следует:
unique_list = list({obj for obj in mylist if obj})
Ответ 6
Вы можете использовать рецепт unique_everseen
, доступный в itertools
документах. Это также доступно в сторонних библиотеках, например toolz.unique
. Обратите внимание, что этот метод сохранит первый экземпляр объекта для данного атрибута.
from toolz import unique
from operator import attrgetter
res = list(unique(objects, key=attrgetter('id')))
Если ленивого итератора достаточно, вы можете пропустить преобразование list
.
Ответ 7
Достаточно простой способ сделать это:
for obj in mylist:
if obj.id not in s:
s.add(obj.id)
И это должно добавить любой идентификатор, который не видел. Затраченное время является линейным по размеру списка источников.
Ответ 8
objects = [Aobject, Bobject, Cobject]
unique_objects = {o['id']:o for o in objects}.values()