2

This works

range(50)[np.asarray(10)]

This works

{}.get(50)

This doesn't because of unhashable type: 'numpy.ndarray'

{}.get(np.asarray(50))

Is there a reason why __hash__ isn't implemented for this case?

Yaroslav Bulatov
  • 57,332
  • 22
  • 139
  • 197
  • If you want to do this practically, see this question: https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array But this is not an answer to your question, why? – plan Oct 09 '17 at 00:22
  • `hash` isn't implemented for `list` either (nor `dict`). It is implemented for immutable `tuple`. That's true even for `[50]`. The `key` for a dictionary has to be immutable. The index for an array or list can be a mutable number. As a rule `scalar ndarrays` aren't that useful, except as a byproduct of a multidimensional array. – hpaulj Oct 09 '17 at 00:47

2 Answers2

2

Python dictionaries require their keys to implement both __eq__ and __hash__ methods, and Python's data model requires that:

  1. The hash of an object does not change during its lifetime
  2. If x == y then hash(x) == hash(y)

Numpy's ndarray class overrides __eq__ to support elementwise comparison and broadcasting. This means that for numpy arrays x and y, x == y is not a boolean but another array. This in itself probably rules out ndarrays functioning correctly as dictionary keys.

Even ignoring this quirk of ndarray.__eq__, it would be tricky to come up with a (useful) implementation of ndarray.__hash__. Since the data in a numpy array is mutable, we could not use that data to calculate __hash__ without violating the requirement that the hash of an object does not change during its lifetime.

There is nothing wrong with defining __hash__ for mutable objects provided that the hash itself does not change during the object's lifetime. Similarly, dictionary keys can be mutable provided they implement __hash__ and the hash is immutable. E.g. simple user-defined classes are mutable but can still be used as dictionary keys.

myrtlecat
  • 2,156
  • 12
  • 16
2

This scalar array is regular array with a 0d shape. Otherwise there's nothing unique about it.

In [46]: x=np.array(10)
In [47]: x
Out[47]: array(10)
In [48]: x[...]=100
In [49]: x
Out[49]: array(100)

You have to extract the number from the array:

In [53]: {}.get(x)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-19202767b220> in <module>()
----> 1 {}.get(x)

TypeError: unhashable type: 'numpy.ndarray'
In [54]: {}.get(x.item())
In [58]: {}.get(x[()])

Looking at the hash methods

In [65]: x.__hash__         # None
In [66]: x.item().__hash__
Out[66]: <method-wrapper '__hash__' of int object at 0x84f2270>
In [67]: x[()].__hash__
Out[67]: <method-wrapper '__hash__' of numpy.int32 object at 0xaaab42b0>
hpaulj
  • 221,503
  • 14
  • 230
  • 353