2016-02-12 29 views
2

我有這樣定義一個簡單的類:自定義類的列表上使用__contains__對象

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] 

的問題是,ID爲6用戶應該只創建了1次,因爲它沒有創建已經。第二次和第三次,6嘗試,它應該失敗。用戶應該不會重新創建,因爲它是users變量初始化的一部分。

如何修改__contains__方法以便在與我的課程的多個實例進行比較時能夠正確使用in

+2

'如果有的話(我在你u用戶)' – khelwood

+3

Off topic:你確定你想實施'__contains__'檢查ID?我沒有發現'123 in user'這樣的表達式非常直觀 –

+0

你想讓'user_1'中的'name_1'也是'True'嗎? –

回答

3

這是一個誤用__contains__。你會期望在類UserList上實現__contains__

更好的方法是直接在生成器表達式或列表理解中(而不是使用in運算符)訪問id屬性。例如。

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) 

然後,對於您的隨機示例,您將使用set或dictionary來存儲已存在的用戶的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))) 
4

如果users是用戶列表,並且您檢查if i in user,那麼您不檢查User.__contains__。您正在檢查list.__contains__。無論你在User.__contains__中做什麼都不會影響檢查結果,如果i在列表中。

如果你想檢查是否i匹配users任何User,你可以這樣做:

if any(i in u for u in users) 

還是有點更清楚:

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

,並避免使用User.__contains__可言。

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) 
+0

這不會在'user = {...}'行失敗,因爲沒有定義'__getitem__'嗎? – NewGuy

+0

@NewGuy:爲什麼需要'__getitem__'?這是一種用於序列和映射的特殊方法; User'既不是。爲了使對象成爲'set'的成員(這是大括號的作用,現在是一個集合理解而不是列表理解),它必須定義'__eq__'和'__hash__';除非你試圖製作自己的'list'或'dict'類的類,否則它不需要'__getitem__'。 – ShadowRanger