Skip to content

Commit dcee516

Browse files
committed
Use PyObject_HashNotImplemented for ctypes data types.
1 parent 2d81eae commit dcee516

3 files changed

Lines changed: 27 additions & 12 deletions

File tree

‎Lib/test/test_ctypes/test_simplesubclasses.py‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
2-
from ctypes import Structure, CFUNCTYPE, c_int, _SimpleCData
2+
from collections.abc import Hashable
3+
from ctypes import Structure, CFUNCTYPE, c_int, _SimpleCData, POINTER
34
from ._support import (_CData, PyCSimpleType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
45
Py_TPFLAGS_IMMUTABLETYPE)
56

@@ -26,6 +27,28 @@ def test_type_flags(self):
2627
self.assertTrue(_SimpleCData.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
2728
self.assertFalse(_SimpleCData.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
2829

30+
def test_unhashable(self):
31+
# gh-78327: CData objects are mutable and must not be hashable.
32+
class S(Structure):
33+
_fields_ = [("a", c_int)]
34+
35+
for cls in c_int, S, c_int * 2, POINTER(c_int), CFUNCTYPE(None):
36+
with self.subTest(cls=cls):
37+
self.assertIsNone(cls.__hash__)
38+
obj = cls()
39+
self.assertNotIsInstance(obj, Hashable)
40+
with self.assertRaisesRegex(TypeError,
41+
f"unhashable type: '{cls.__name__}'"):
42+
hash(obj)
43+
44+
# Subclasses can still opt in to hashing.
45+
class H(S):
46+
def __hash__(self):
47+
return 42
48+
49+
self.assertEqual(hash(H()), 42)
50+
self.assertIsInstance(H(), Hashable)
51+
2952
def test_metaclass_details(self):
3053
# Abstract classes (whose metaclass __init__ was not called) can't be
3154
# instantiated directly
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`ctypes` data types now set :meth:`~object.__hash__` to ``None``, so
2+
:class:`collections.abc.Hashable` checks report them correctly.

‎Modules/_ctypes/_ctypes.c‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3105,16 +3105,6 @@ PyCData_NewGetBuffer(PyObject *myself, Py_buffer *view, int flags)
31053105
return 0;
31063106
}
31073107

3108-
/*
3109-
* CData objects are mutable, so they cannot be hashable!
3110-
*/
3111-
static Py_hash_t
3112-
PyCData_nohash(PyObject *self)
3113-
{
3114-
PyErr_SetString(PyExc_TypeError, "unhashable type");
3115-
return -1;
3116-
}
3117-
31183108
/*[clinic input]
31193109
@critical_section
31203110
_ctypes.PyCData.__reduce__
@@ -3213,7 +3203,7 @@ static PyMethodDef PyCData_methods[] = {
32133203

32143204
static PyType_Slot pycdata_slots[] = {
32153205
{Py_tp_dealloc, PyCData_dealloc},
3216-
{Py_tp_hash, PyCData_nohash},
3206+
{Py_tp_hash, PyObject_HashNotImplemented},
32173207
{Py_tp_doc, PyDoc_STR("XXX to be provided")},
32183208
{Py_tp_traverse, PyCData_traverse},
32193209
{Py_tp_clear, PyCData_clear},

0 commit comments

Comments
 (0)