是否有设置对象的比较键?

时间:2016-03-31 23:14:56

标签: python

有没有办法给set()一个比较器,所以当添加项目时,它会检查该项目的属性是否相似而不是项目是否相同?例如,我想在集合中使用可以包含一个属性的相同值的对象。

class TestObj(object):
    def __init__(self, value, *args, **kwargs):
        self.value = value 
        super().__init__(*args, **kwargs)

values = set()
a = TestObj('a')
b = TestObj('b')
a2 = TestObj('a')
values.add(a) # Ok
values.add(b) # Ok
values.add(a2) # Not ok but still gets added

# Hypothetical code
values = set(lambda x, y: x.value != y.value)
values.add(a) # Ok
values.add(b) # Ok
values.add(a2) # Not added

我已经实现了我自己的类似功能,但想知道是否有内置方式。

from Queue import Queue
class UniqueByAttrQueue(Queue):
    def __init__(self, attr, *args, **kwargs):
        Queue.__init__(self, *args, **kwargs)
        self.attr = attr

    def _init(self, maxsize):
        self.queue = set()

    def _put(self, item):
        # Potential race condition, worst case message gets put in twice
        if hasattr(item, self.attr) and item not in self:
            self.queue.add(item)

    def __contains__(self, item):
        item_attr = getattr(item, self.attr)
        for x in self.queue:
            x_attr = getattr(x, self.attr)
            if x_attr == item_attr:
                return True
        return False

    def _get(self):
        return self.queue.pop()

1 个答案:

答案 0 :(得分:2)

只需根据相关属性定义对象上的__hash____eq__,它就可以使用set s。例如:

class TestObj(object):
    def __init__(self, value, *args, **kwargs):
        self.value = value 
        super().__init__(*args, **kwargs)

    def __eq__(self, other):
        return self.value == other.value

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

如果你不能改变对象(或者不想,因为其他事情对于平等很重要),那么请改用dict。你可以这样做:

mydict[obj.value] = obj

所以新对象替换旧的,或

mydict.setdefault(obj.value, obj)

如果问题中的value已经存在于密钥中,则会保留旧对象。只需确保使用.viewvalues()(Python 2)或.values()(Python 3)进行迭代,而不是直接迭代(这将获得键,而不是值)。您实际上可以使用这种方法来创建一个自定义的set - 类似对象,如您所描述的那样(尽管您需要实现比我显示的方法更多的方法以使其有效,但默认方法通常相当慢):

from collections.abc import MutableSet  # On Py2, collections without .abc

class keyedset(MutableSet):
    def __init__(self, it=(), key=lambda x: x):
        self.key = key
        self.contents = {}
        for x in it:
            self.add(x)

    def __contains__(self, x):
        # Use anonymous object() as default so all arguments handled properly
        sentinel = object()
        getval = self.contents.get(self.key(x), sentinel)
        return getval is not sentinel and getval == x

    def __iter__(self):
        return iter(self.contents.values())  # itervalues or viewvalues on Py2

    def __len__(self):
        return len(self.contents)

    def add(self, x):
        self.contents.setdefault(self.key(x), x)

    def discard(self, x):
        self.contents.pop(self.key(x), None)