如何使用等效对象访问集合中的元素?

问题描述 投票:0回答:3

如果我有一个对象与 Python 集合中的一个元素比较,但不是同一个对象,是否有合理的方法来获取集合中对象的引用?用例将使用该集来识别和共享重复的数据。

示例(Python 2.7):

>>> a = "This is a string"
>>> b = "This is a string"
>>> a is b
False
>>> a == b
True
>>> s = set((a,))
>>> b in s
True

如何使用

a
b
获取对
s
的引用?我可以想到一种方法,但我不确定您是否得到
a
b
是否不依赖于实现。 编辑: 当 s 有多个元素时,这不起作用;交叉是很自然地实现的,比如
[x for x in smaller_set if x in larger_set]

>>> for x in set((b,)).intersection(s): c = x
...
>>> c is a
True

也许一个好的解决方法是使用将每个键映射到其自身的字典,而不是集合。

python set object-identity
3个回答
6
投票

我在 python-list 上发现了类似的问题:Get item from set。参考get_equivalent(container, item) (Python Recipe)有一个聪明的答案。

技巧是为“key”对象构造一个包装器对象,并使用

in
运算符检查包装器是否在集合中。如果包装器哈希值等于键,则其
__eq__
方法可以访问集合中的对象,并保存对其的引用。讨论中的一个要点是,对于无法识别的类型,集合元素的
__eq__
方法必须返回
NotImplemented
,否则包装器的
__eq__
可能不会被调用。


3
投票

您的用例听起来像是字典的用例。使用与“外部”对象比较的对象的属性作为键,并使用所需对象本身作为值。

如果这是一个简单的用例,并且您可以进行线性搜索,但是,您可以做显而易见的事情 - 这不会太糟糕:

def get_equal(in_set, in_element):
   for element in in_set:
       if element == in_element:
           return element
   return None 

如果您确实需要您所要求的内容(我想知道一些用例) - 方法是创建一个自定义字典类,该字典类的成员之一是集合,请为该成员实现代理方法set,在字典和集合方法中,保持字典和集合内容的同步。正确实施会非常耗时,但相对简单,并且时间复杂度为 O(1)。

如果必须复制对周围所有数据的引用不是问题(这是线性的,但可能比上面的直接搜索更糟糕),您可以使用表达式

(data - (data - {key})).pop()

如:

In [40]: class A:
    ...:     def __init__(self, id, extra):
    ...:         self.id = id
    ...:         self.extra = extra
    ...:     def __eq__(self, other):
    ...:         return self.id == other.id
    ...:     def __hash__(self):
    ...:         return hash(self.id)
    ...:     def __repr__(self):
    ...:         return f"({self.id}, {self.extra})"
    ...: 
    ...: 

In [41]: data = set(A(i, "initial") for i in range(10))

In [42]: (data - (data - {A(5, None)})).pop()
Out[42]: (5, initial)

0
投票

这是我通过利用“eq”和“contains”方法的行为做出的快速解决方案。代码注释(希望)是自我记录的,但在其他方面非常简单。

import typing as _ts
from typing import Any


class Getter:
    __slots__ = "key", "value"

    def __init__(self, key, value=None):
        self.key = key
        self.value = value

    def __repr__(self):
        return "{}({}, {})".format(
            type(self).__name__,
            repr(self.key), repr(self.value),
        )

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

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


RAISES = object()


def getkey(keyed: _ts.Container, key: Any, default: Any = RAISES):
    getter = Getter(key)
    if getter in keyed:
        # providing '__contains__' is implemented to call
        #  the '__eq__' method (which in any sane case it
        #  should be), this results in our special
        #  'Getter.__eq__' method being called with the
        #  element we're trying to get as the 'other' argument
        return getter.value
    if default is RAISES:
        raise KeyError(key)
    return default


if __name__ == '__main__':
    # testing

    class T(int):
        def __repr__(self):
            return "T({})".format(int.__repr__(self))

    def main():
        # works for both builtin set and dict
        hm1 = {T(1), T(2), T(3)}
        hm2 = {T(1): 1, T(2): 2, T(3): 3}
        print(getkey(hm1, 2))
        print(getkey(hm2, 2))
        # should print "T(2)"

        # even works for list
        lst = [T(1), T(2), T(3)]
        print(getkey(lst, 3))
        # should print "T(3)"

        # in theory could work for any type that
        #  implements '__contains__' by calling '__eq__'

    main()
© www.soinside.com 2019 - 2024. All rights reserved.