12

Вопрос по python, list, set – Как получить список объектов с уникальным атрибутом

Background

у меня естьlist. This list имеет много объектов. Каждый объект имеетid, Сейчас объекты бывают разных типов.

objects = [Aobject, Bobject, Cobject]

где

>>> Aobject != Bobject
True
>>> Aobject.id ==  Bobject.id
True

Problem

я хочуlist уникальных объектов на основеobject.id.

Что-то вроде этого:

set(objects, key=operator.attrgetter('id'))

(Это не работает. Но я хочу что-то вроде этого)

  • Error: User Rate Limit Exceededself.id.__hash__()Error: User Rate Limit Exceededreturn id(self)Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceededself.id != id(self)?

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceededreturn id(self)Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit ExceededorError: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceededmylist?

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit ExceededobjectError: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Вы не должны перезаписывать встроенныеlist; назовите свою переменнуюlst или жеlist_ или что-то более значимое вместо этого.

    от ThiefMaster♦
6 ответов
  • 16

    Это работает, потому что

    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

    Учитывая ваш список объектов

    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])]
    

  • -1

    Довольно простой способ сделать это

    for obj in mylist:
        if obj.id not in s:
            s.add(obj.id)
    

    И это должно добавить любой идентификатор не видел. Требуемое время является линейным по размеру списка источников.

  • 3

    Как насчет использования

    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 <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') в нашем конкретном случае).

    Ответ Марцина работает нормально, но не выглядит для меня 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
    

  • 0

    Если вы можете изменить класс объектов

    вы можете добавить соответствующие методы, которые используются при сравнении наборов:

    # 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])])
    

  • 0

    Вы можете использовать

    unique_everseen рецепт доступен вitertools документы, Это также доступно в сторонних библиотеках, напримерtoolz.unique, Обратите внимание, что этот метод сохранитfirst экземпляр объекта для данного атрибута.

    from toolz import unique
    from operator import attrgetter
    
    res = list(unique(objects, key=attrgetter('id')))
    

    Если ленивый итератор достаточен, вы можете опуститьlist преобразование.