You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PyPy3 TypeError: 'numpy.float64' objects are unhashable #8887
I'm logging this here for reference, and on the off chance that someone on the NumPy team could say why numpy.float64 might behave so differently from the other size floats under PyPy3.
Expected behaviour, showing Python 3.5.0 in 64-bit Linux:
$ python3
Python 3.5.0 (default, Sep 28 2015, 11:25:31)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> print(numpy.__version__)
1.12.1
>>> f = 123.456
>>> for t in [np.float, np.float16, np.float32, np.float64, np.float128]:
... new = t(f)
... print(hash(new), hash(new)==hash(f), t.__name__)
...
1051464412201451643 True float
1008806316530991227 False float16
1051467367688700027 False float32
1051464412201451643 True float64
1051464412201451643 True float128
>>> quit()
$ ~/Downloads/pypy3.5-5.7-beta-linux_x86_64-portable/bin/pypy
Python 3.5.3 (b16a4363e930f6401bceb499b9520955504c6cb0, Mar 21 2017, 12:36:24)
[PyPy 5.7.0-beta0 with GCC 6.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``the world doesn't want us to
know''
>>>> import numpy as np
>>>> print(np.__version__)
1.12.1
>>>> f = 123.456
>>>> for t in [np.float, np.float16, np.float32, np.float128, np.float64]:
.... new = t(f)
.... print(hash(new), hash(new)==hash(f), t.__name__)
....
1051464412201451643 True float
1008806316530991227 False float16
1051467367688700027 False float32
1051464412201451643 True float128
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
TypeError: 'numpy.float64' objects are unhashable
>>>> quit()
The other float types seem to hash fine, but numpy.float64 does not under PyPy3.5 v5.7 beta.
I tried debugging this in PyPy, but got bogged down trying to understand how exactly numpy initialises the types.
The situation on CPython is quite dodgy, since float64 has both a tp_hash and a descriptor for __hash__ that returns None - which causes the above behaviour: hash(nf) looks up the slot tp_hash, not the descriptor.
@eric-wieser: I'm not sure what's going on with float64.__hash__ returning None, but special methods are internally way more complicated than that. Most methods are natively entries in a python dict; special methods are natively slots in a C struct, and there's a whole elaborate layer of code to map back and forth between the python and C representations and try to make them look the same. Part of this is that when defining a class from Python, the type object constructor has a special case where it checks for __hash__ == None and then initializes the tp_hash slot in a special way. But float64 is defined in C, so it doesn't go through that code path at all. And then of course pypy adds a whole additional layer of complexity...
@peterjc: you should also check your intXX objects -- I bet either int32 or int64 has the same bug (depending on how large your platform's long is).
The culprit is the DUAL_INHERIT macro in multiarraymodule.c::setup_scalartypes(). ATM, it calls PyType_Ready, and then sets tp_hash. But since tp_hash is NULL when PyType_Ready is called, it sets __dict__['__hash__'] = None and tp_hash to PyObject_HashNotImplemented, which is later overwritten.
It seems that the fix is just to set tp_hash first.
Activity
eric-wieser commentedon Apr 3, 2017
I think the difference is:
Because
np.float64.__bases__containsfloat, so this defers tofloat.__hash__.Could this be a bug to do with hashing and multiple inheritance in PyPy?
njsmith commentedon Apr 3, 2017
This is almost certainly related somehow to the fact that float64 is a subclass of float (the built-in type), and the other floatXX classes aren't.
eric-wieser commentedon Apr 3, 2017
I'm super confused here though - why is it legal to call
hash(float64)? Test case:I thought that
__hash__ == Nonealways made the type unhashable? How does numpy pull of having__hash__ == Noneand still being hashable?rlamy commentedon Apr 3, 2017
I tried debugging this in PyPy, but got bogged down trying to understand how exactly numpy initialises the types.
The situation on CPython is quite dodgy, since
float64has both atp_hashand a descriptor for__hash__that returns None - which causes the above behaviour:hash(nf)looks up the slottp_hash, not the descriptor.njsmith commentedon Apr 3, 2017
@eric-wieser: I'm not sure what's going on with
float64.__hash__returning None, but special methods are internally way more complicated than that. Most methods are natively entries in a python dict; special methods are natively slots in a C struct, and there's a whole elaborate layer of code to map back and forth between the python and C representations and try to make them look the same. Part of this is that when defining a class from Python, the type object constructor has a special case where it checks for__hash__ == Noneand then initializes thetp_hashslot in a special way. Butfloat64is defined in C, so it doesn't go through that code path at all. And then of course pypy adds a whole additional layer of complexity...@peterjc: you should also check your intXX objects -- I bet either int32 or int64 has the same bug (depending on how large your platform's
longis).rlamy commentedon Apr 3, 2017
@njsmith Nope, the issue exists only with
np.float64andnp.complex128(on a 64-bit Linux).njsmith commentedon Apr 3, 2017
wut
rlamy commentedon Apr 3, 2017
@njsmith Sorry, it's not a descriptor, it's just a
Nonevalue in the class dict (which is the conventional way of indicating "not hashable").eric-wieser commentedon Apr 3, 2017
Found the bug, patch incoming...
rlamy commentedon Apr 3, 2017
The culprit is the
DUAL_INHERITmacro inmultiarraymodule.c::setup_scalartypes(). ATM, it callsPyType_Ready, and then setstp_hash. But sincetp_hashisNULLwhenPyType_Readyis called, it sets__dict__['__hash__'] = Noneandtp_hashtoPyObject_HashNotImplemented, which is later overwritten.It seems that the fix is just to set
tp_hashfirst.eric-wieser commentedon Apr 3, 2017
@rlamy: Indeed, that's the patch I have
eric-wieser commentedon Apr 3, 2017
@peterjc: Does this fix PyPy?
rlamy commentedon Apr 3, 2017
@eric-wieser Yes, it fixes it.
peterjc commentedon Apr 3, 2017
Excellent work everyone - thank you. I'll look forward to this working in the next NumPy release :)
Skip NaiveBayes test if numpy.float64 is unhashable.
Skip NaiveBayes test if numpy.float64 is unhashable.
Change is_hashable()
beginning overhaul of random processes (#67)
Merging Kevin's changes (#76)