Putting two keys with the same hash into a dict

喜夏-厌秋 提交于 2019-12-08 15:02:37

问题


>>> one_decimal = Decimal('1')
>>> one_complex = complex(1,0)
>>> d = {one_decimal: '1D', one_complex: '1C'}
>>> len(d)
2
>>> map(hash, d)
[1, 1]

Above, I create a dict with a hash collision, and two slots occupied.

>>> d[1]
'1D'
>>> d[1+0j]
'1C'

How is that getitem handled for the integer 1? And how does the indexing manage to resolve the correct value for a complex literal indexing?

Python 2.7.12 / Linux.


回答1:


As the accepted answer mentioned by @CoryKramer states, equality of hashes does not imply equality of objects. Python dictionaries can contain any number of elements with equal hashes as long as the objects themselves are not equal.

The short answer to your question is probably that the implementation of the complex type is a bit incomplete in the Python library as of 2.7. As @wim points out, comparing int and complex using == works fine, but comparing Decimal and complex does not. Since comparing one_decimal == one_complex will always return False because of their types, they can both live in the same dictionary in Python 2.7.

This issue has been fixed in Python 3. I am experimenting in 3.5, where one_decimal and one_complex are equal. After running the same snippet, the dictionary contains the value for one_complex under the key one_decimal, as expected (first key, last value).

TL;DR

It's a bug in Py2.7's complex type. Fixed in Py3.




回答2:


The problem is only when using a Decimal with a complex number. Float's, int's etc. work fine as they are coerced for comparison.

In python3's decimal lib comparing to a complex number is handled specifically in _convert_for_comparison:

    # Comparisons with float and complex types.  == and != comparisons
    # with complex numbers should succeed, returning either True or False
    # as appropriate.  Other comparisons return NotImplemented.
    if equality_op and isinstance(other, _numbers.Complex) and other.imag == 0:
        other = other.real

In python2 the convert function implementation is:

def _convert_other(other, raiseit=False, allow_float=False):
    """Convert other to Decimal.

    Verifies that it's ok to use in an implicit construction.
    If allow_float is true, allow conversion from float;  this
    is used in the comparison methods (__eq__ and friends).

    """
    if isinstance(other, Decimal):
        return other
    if isinstance(other, (int, long)):
        return Decimal(other)
    if allow_float and isinstance(other, float):
        return Decimal.from_float(other)

    if raiseit:
        raise TypeError("Unable to convert %s to Decimal" % other)
    return NotImplemented

Why len(set([1, Decimal('1'), (1+0j)])) == 2 is because the Decimal is compared to the int first, if you change the ordering you get different output:

In [23]: {1, Decimal('1'), (1+0j)}
Out[23]: {(1+0j), Decimal('1')}

In [24]: {Decimal('1'),1, (1+0j)}
Out[24]: {(1+0j), Decimal('1')}

In [25]: {Decimal('1'), (1+0j), 1}
Out[25]: {1}

Also using a literal is better as the order you insert matches the docs, as in the last value is preserved using a literal over the set(..).



来源:https://stackoverflow.com/questions/40225520/putting-two-keys-with-the-same-hash-into-a-dict

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!