在自定义类对象列表上使用__contains__

时间:2016-02-12 15:45:30

标签: python

我有一个像这样定义的简单类:

class User(object):
    def __init__(self, id=None, name=None):
        self.id = id
        self.name = name

    def __contains__(self, item):
        return item == self.id

使用这个类,我可以对类的单个实例进行简单的检查,如下所示:

>>> user1 = User(1, 'name_1')
>>> 1 in user1
True
>>> 2 in user1
False

这是按预期工作的。

如何查看某个值是否在User个对象列表中?它总是似乎返回False。

示例:

from random import randint
from pprint import pprint
users = [User(x, 'name_{}'.format(x)) for x in xrange(5)]
pprint(users, indent=4)

for x in xrange(5):
    i = randint(2,6) 
    if i in users:
        print("User already exists: {}".format(i))
    else:
        print("{} is not in users list. Creating new user with id: {}".format(i, i))
        users.append(User(i, 'new_user{}'.format(i)))
pprint(users, indent=4)

这会创建类似于此的输出:

[   0 => name_0, 
    1 => name_1, 
    2 => name_2, 
    3 => name_3, 
    4 => name_4]
6 is not in users list. Creating new user with id: 6
6 is not in users list. Creating new user with id: 6
6 is not in users list. Creating new user with id: 6
3 is not in users list. Creating new user with id: 3
3 is not in users list. Creating new user with id: 3
[   0 => name_0,
    1 => name_1,
    2 => name_2,
    3 => name_3,
    4 => name_4,
    6 => new_user6,
    6 => new_user6,
    6 => new_user6,
    3 => new_user3,
    3 => new_user3]

问题是身份6的用户应该只创建一次,因为它尚未创建。尝试6的第二次和第三次,它应该失败。用户身份标识3根本不应该重新创建,因为它是users变量初始化的一部分。

如何与__contains__方法进行修改,以便在与我的班级的多个实例进行比较时能够正确使用in

3 个答案:

答案 0 :(得分:4)

如果users是用户列表,并且您选中了if i in user,那么您不会检查User.__contains__。您正在查看list.__contains__。无论您在User.__contains__中执行什么操作都不会影响检查i是否在列表中的结果。

如果您想检查i是否与User中的users匹配,您可以执行以下操作:

if any(i in u for u in users)

或者更清楚一点:

if any(u.id==i for u in users)

并且完全避免使用User.__contains__

答案 1 :(得分:4)

这是对__contains__的误用。您希望在__contains__等类上实施UserList

更好的方法是直接在生成器表达式或列表推导中访问id属性(而不是使用in运算符)。例如

class User(object):
    def __init__(self, id=None, name=None):
        self.id = id
        self.name = name

user = User(1, 'name_1')
assert 1 == user.id

user_list = [user, User(2, 'name_2')]
assert any(2 == u.id for u in user_list)

然后,对于您的随机示例,您将使用集合或字典来存储已存在的用户的ID。

users = [User(x, 'name_{}'.format(x)) for x in xrange(5)]
ids = set(u.id for u in users)

for x in xrange(5):
    i = randint(2,6) 
    if i in ids:
        print("User id already exists: {}".format(i))
    else:
        print("{} is not in users list. Creating new user with id: {}".format(i, i))
        ids.add(i)
        users.append(User(i, 'new_user{}'.format(i)))

答案 2 :(得分:2)

这似乎是您真正想要定义__eq__以接受与其他User个对象和int的比较的情况。这将使包含对User集合的检查自动生效,并且在一般用法中比在非容器类型上实现__contains__更有意义。

from operator import index

class User(object):
    def __init__(self, id=None, name=None):
        self.id = id
        self.name = name

    def __eq__(self, item):
        if isinstance(item, User):
            return self.id == item.id and self.name == item.name
        try:
            # Accept any int-like thing
            return self.id == index(item)
        except TypeError:
            return NotImplemented

    def __ne__(self, item): # Only needed on Py2; Py3 defines it implicitly
        ret = self == item
        if ret is NotImplemented: return ret
        return not ret

    def __hash__(self):
        return self.id

现在,您可以将您的类型与普通集合(包括setdict键)一起使用,并且可以轻松查找。

from operator import attrgetter

# Use set for faster lookup; can sort for display when needed
users = {User(x, 'name_{}'.format(x)) for x in xrange(5)}
pprint(sorted(users, key=attrgetter('id')), indent=4)

for x in xrange(5):
    i = randint(2,6) 
    if i in users:
        print("User already exists: {}".format(i))
    else:
        print("{} is not in users list. Creating new user with id: {}".format(i, i))
        users.add(User(i, 'new_user{}'.format(i)))
pprint(sorted(users, key=attrgetter('id')), indent=4)