如果我有一个对象与 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-list 上发现了类似的问题:Get item from set。参考get_equivalent(container, item) (Python Recipe)有一个聪明的答案。
技巧是为“key”对象构造一个包装器对象,并使用
in
运算符检查包装器是否在集合中。如果包装器哈希值等于键,则其 __eq__
方法可以访问集合中的对象,并保存对其的引用。讨论中的一个要点是,对于无法识别的类型,集合元素的 __eq__
方法必须返回 NotImplemented
,否则包装器的 __eq__
可能不会被调用。
您的用例听起来像是字典的用例。使用与“外部”对象比较的对象的属性作为键,并使用所需对象本身作为值。
如果这是一个简单的用例,并且您可以进行线性搜索,但是,您可以做显而易见的事情 - 这不会太糟糕:
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)
这是我通过利用“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()