Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/363.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python `列表“”中的对象的行为与dict“”中的对象不同?_Python_List_If Statement_Dictionary_Cpython - Fatal编程技术网

Python `列表“”中的对象的行为与dict“”中的对象不同?

Python `列表“”中的对象的行为与dict“”中的对象不同?,python,list,if-statement,dictionary,cpython,Python,List,If Statement,Dictionary,Cpython,我有一个迭代器,里面有一些对象,我想创建一个uniqueuser集合,其中我只列出每个用户一次。因此,我尝试了一下,用列表和口述: >>> for m in ms: print m.to_user # let's first look what's inside ms ... Pete Kramer Pete Kramer Pete Kramer >>> >>> uniqueUsers = [] # Create an empty li

我有一个迭代器,里面有一些对象,我想创建一个uniqueuser集合,其中我只列出每个用户一次。因此,我尝试了一下,用列表和口述:

>>> for m in ms: print m.to_user  # let's first look what's inside ms
...
Pete Kramer
Pete Kramer
Pete Kramer
>>> 
>>> uniqueUsers = []  # Create an empty list
>>> for m in ms:
...     if m.to_user not in uniqueUsers:
...         uniqueUsers.append(m.to_user)
...
>>> uniqueUsers
[Pete Kramer]  # This is what I would expect
>>> 
>>> uniqueUsers = {}  # Now let's create a dict
>>> for m in ms:
...     if m.to_user not in uniqueUsers:
...         uniqueUsers[m.to_user] = 1
...
>>> uniqueUsers
{Pete Kramer: 1, Pete Kramer: 1, Pete Kramer: 1}
因此,在执行if语句时,我通过将dict转换为一个列表对其进行了测试,结果正如我所期望的那样:

>>> uniqueUsers = {}
>>> for m in ms:
...     if m.to_user not in list(uniqueUsers):
...         uniqueUsers[m.to_user] = 1
...
>>> uniqueUsers
{Pete Kramer: 1}
通过对
uniqueUsers.keys()
进行测试,我可以得到类似的结果

问题是我不明白为什么会出现这种差异。我一直认为,如果在dict中使用
if对象,它只会创建一个dicts键列表并再次进行测试,但情况显然不是这样


谁能解释一下dict中的
对象是如何在内部工作的,以及为什么它的行为与list中的
对象不相似(正如我所期望的那样)?

为了理解发生了什么,您必须理解
中的
操作符,即,对于不同类型的行为

对于列表,这非常简单,因为列表基本上是:不关心重复项的有序数组。在这里形成成员资格测试的唯一可能的方法是迭代列表并检查每个项是否相等。大概是这样的:

# x in lst
for item in lst:
    if x == item:
        return True
return False
字典有点不同:它们是哈希表,而键是唯一的。哈希表要求键是可哈希的,这本质上意味着需要一个显式函数将对象转换为整数。然后使用此哈希值将键/值映射放在哈希表的某个位置

因为散列值决定了一个项目在散列表中的位置,所以相同的对象产生相同的散列值是至关重要的。因此,以下含义必须为真:
x==y=>hash(x)==hash(y)
。然而,反过来不一定是真的;让不同的对象产生相同的哈希值是完全有效的

当对字典执行成员资格测试时,字典将首先查找哈希值。如果它能找到它,那么它将对找到的所有项执行相等性检查;如果未找到哈希值,则假定它是另一个对象:

# x in dct
h = hash(x)
items = getItemsForHash(dct, h)
for item in items:
    if x == item:
        return True
# items is empty, or no match inside the loop
return False
由于在对列表使用成员资格测试时获得了所需的结果,这意味着您的对象正确地实现了相等比较()。但是,由于使用字典时没有得到正确的结果,因此似乎有一个实现与相等比较实现不同步:

>>> class SomeType:
        def __init__ (self, x):
            self.x = x
        def __eq__ (self, other):
            return self.x == other.x
        def __hash__ (self):
            # bad hash implementation
            return hash(id(self))

>>> l = [SomeType(1)]
>>> d = { SomeType(1): 'x' }
>>> x = SomeType(1)
>>> x in l
True
>>> x in d
False
请注意,对于Python2中的新样式类(从
对象继承的类
),此“错误哈希实现”(基于对象id)是默认值。因此,当您没有实现自己的
\uuuuuuuuuuuuuuuu散列
函数时,它仍然使用该函数。这最终意味着,除非您的
\uuuu eq\uuu
仅执行身份检查(默认设置),否则哈希函数将不同步

因此,解决方案是以一种与
\uuuuuueq\uuuu
中使用的规则一致的方式实现
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。例如,如果比较两个成员
self.x
self.y
,则应在这两个成员上使用复合哈希。最简单的方法是返回这些值的元组的哈希值:

class SomeType (object):
    def __init__ (self, x, y):
        self.x = x
        self.y = y

    def __eq__ (self, other):
        return self.x == other.x and self.y == other.y

    def __hash__ (self):
        return hash((self.x, self.y))
请注意,如果对象是可变的,则不应将其设置为可哈希的:

如果类定义了可变对象并实现了
\uuuu eq\uuuu()
方法,那么它不应该实现
\uuuu hash\uuuuu()
,因为可哈希集合的实现要求键的哈希值是不可变的(如果对象的哈希值更改,它将位于错误的哈希桶中)


TL;DR:test中的
调用列表的
\uuuuu eq\uuuu
。对于dicts,它首先调用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,如果哈希匹配,则调用
\uuuuuuuuuuuuuuuuuuu

  • 测试中的
    仅调用列表的
    \uuuuu eq\uuu
    • 如果没有
      \uuuuu eq\uuuuuu
      ,则不一致性比较始终为
      False
  • 对于DICT,您需要一个正确实现的
    \uuuuuuu散列
    \uuuuuuuuuuuuuuu eq
    才能正确比较其中的对象:

    • 首先从
      \uuuu hash\uuuu

      • 如果没有
        \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu散列
        ,对于新样式的类,它使用
        id()
        ,这对于创建的所有对象都是唯一的,因此永远不会与现有对象匹配,除非它是同一个对象
      • 正如@poke在评论中指出的: 在Python2中,新样式类(继承自
        对象
        )继承对象的
        \uuuu散列
        实现,该实现基于
        id()
        ,因此这就是它的来源

    • 如果散列匹配,则使用
      其他
      为该对象调用
      eq

      • 结果取决于
        \uuuu eq\uuu
        返回的内容
    • 如果散列不匹配,则不调用
      \uuuuuuueq\uuuu
  • 因此,
    测试中的
    调用列表和dict的
    \uuuuuuuuueq\uuuuu
    ,但对于dict,只有在
    \uuuuhash\uuuuuu
    返回一个匹配的散列后,
    没有
    \uuuuhash\uuuuuu
    不会返回
    ,不会抛出错误,也不会使其“不可修复”。。。在Python 2中。要将您的
    To_user
    类正确地用作dict键,您确实需要有一个与
    \uuuuu eq\uuu
    同步的正确实现的类

    详情:

    检查
    m.to\u user not in uniqueUsers
    “列表中的对象”工作正常,因为正如@poke所指出的,您可能已经实现了
    \uuuuuuu eq\uu
    方法。(它向用户显示
    返回一个对象,而不是字符串。)

    同样的检查也不适用于“dict中的对象”,因为:
    (a)
    \uuuu散列\uuuu
    在该类中是b
    >>> class Test2(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...
    ...     def __eq__(self, other):
    ...         return self.name == other.name
    ...
    >>> test_Dict = {}
    >>> test_List = []
    >>>
    >>> obj1 = Test2('a')
    >>> obj2 = Test2('a')
    >>>
    >>> test_Dict[obj1] = 'x'
    >>> test_Dict[obj2] = 'y'
    >>>
    >>> test_List.append(obj1)
    >>> test_List.append(obj2)
    >>>
    >>> test_Dict
    {<__main__.Test2 object at 0x0000000002EFC518>: 'x', <__main__.Test2 object at 0x0000000002EFC940>: 'y'}
    >>> test_List
    [<__main__.Test2 object at 0x0000000002EFC518>, <__main__.Test2 object at 0x0000000002EFC940>]
    >>>
    >>> Test2('a') in test_Dict
    False
    >>> Test2('a') in test_List
    True