如何使用__contains__ /“in”关键字在Python列表中搜索对象?

4
我希望能够创建一个由我定义的对象列表,并使用 in 关键字(调用 __contains__)来确定该对象是否存在于Python列表中。
以下是一个带有注释的最简示例:
>>> class Foo(object):
...     def __init__(self, name):
...             self.name = name
...     def __contains__(self, item):
...             return self.name == item
... 
>>> list_of_objects = [Foo("bar"), Foo("baz"), Foo("quux")]
>>> # I want to see if "bar" is in this list of Foo() objects
>>> Foo("bar") in list_of_objects
False      # <-- I want this to be True
in 关键字应该在 Python 列表上遍历,并使用 __contains__ 方法来确定对象是否存在,这样才对。如果调用列表的 index() 函数,可以获得额外的赞誉。
更新:感谢 @user2357112 的帮助,看起来答案是实现等价运算符 __eq__。在前面示例中的 Foo 类中添加以下代码即可解决我遇到的问题。
>>> class Foo(object):
...     def __init__(self, name):
...             self.name = name
...     def __eq__(self, other):
...             return other.name == self.name
>>> list_of_objects = [Foo("bar"), Foo("baz"), Foo("quux")]
>>> Foo("bar") in list_of_objects
True

1
当你执行 "bar" in list_of_objects 时,会调用 list.__contains__。它绝不应该调用 Foo.__contains__,仅仅因为列表包含 Foo 实例,而且不清楚你为什么认为它应该这样做。 - Vincent Savard
这是可以理解的,但问题仍然存在,如何做到呢?覆盖list.__contains__?显然这是一个不好的主意... - Nathan McCoy
1个回答

7

__contains__ 是在容器上调用的方法,而不是在元素上调用的方法。你需要实现 __eq__ 代替:

class Foo(object):
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        if isinstance(other, Foo):
            return other.name == self.name
        return self.name == other

如果你要在其他容器中存储Foo(比如set),请记得实现__hash__


4
在此过程中,你还应该定义 __ne__ 方法,否则 ==!= 将不一致。如果对象是可变的,则应将 __hash__ 设置为 None 以使对象无法哈希。这些事情应该处理好,无论您是否打算在这些对象中使用 !=、集合或字典,因为现在正确处理它们比以后尝试调试出现的疯狂问题要容易得多,如果您忘记没有处理它们。 - user2357112
这是正确的答案, in 运算符在迭代列表时使用 __eq__。我将其添加到代码中,它可以正常工作。谢谢。 - Nathan McCoy
@user2357112 实际上,这适用于Python 2。在Python 3中,__ne__会反转__eq__,而具有__eq__但没有__hash__的对象是不可哈希的。 - bereal
1
@bereal:是的,那些都是不错的Python 3功能。不过问题中没有涉及到Python 3特定的内容。 - user2357112

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接