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

And*_*ndy 6 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
Run Code Online (Sandbox Code Playgroud)

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

>>> user1 = User(1, 'name_1')
>>> 1 in user1
True
>>> 2 in user1
False
Run Code Online (Sandbox Code Playgroud)

这按预期工作。

我如何检查一个值是否在对象列表中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)
Run Code Online (Sandbox Code Playgroud)

这将创建与此类似的输出:

[   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]
Run Code Online (Sandbox Code Playgroud)

问题是具有 id 的用户6应该只被创建 1 次,因为它还没有被创建。第二次、第三次6,应该都失败了。用户 id3根本不应该被重新创建,因为它是变量初始化的一部分users

在与我的类的多个实例进行比较时,如何修改__contains__方法才能正确利用?in

khe*_*ood 6

如果users是用户列表并且您进行了检查if i in users,那么您就没有进行检查User.__contains__。你正在检查list.__contains__。无论你做什么都User.__contains__不会影响检查是否i在列表中的结果。

如果你想检查是否i匹配任何Userin users,你可以这样做:

if any(i in u for u in users)
Run Code Online (Sandbox Code Playgroud)

或者更清楚一点:

if any(u.id==i for u in users)
Run Code Online (Sandbox Code Playgroud)

User.__contains__并完全避免使用。


Sha*_*ger 6

__eq__这似乎是您确实想要定义接受与其他User对象 和的比较的情况int。这将使 contains 检查自动工作,并且在一般用法中比在非容器类型上User实现更有意义。__contains__

import sys
from operator import index

class User(object):  # Explicit inheritance from object can be removed for Py3-only code
    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

    # Canonical mirror of __eq__ only needed on Py2; Py3 defines it implicitly
    if sys.version_info < (3,):
        def __ne__(self, other):
            equal = self.__eq__(other)
            return equal if equal is NotImplemented else not equal

    def __hash__(self):
        return self.id
Run Code Online (Sandbox Code Playgroud)

现在您可以将您的类型与普通集合(包括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)
Run Code Online (Sandbox Code Playgroud)


Dun*_*nes 5

这是对 的滥用__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)
Run Code Online (Sandbox Code Playgroud)

然后,对于随机示例,您将使用集合或字典来存储已存在的用户的 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)))
Run Code Online (Sandbox Code Playgroud)