Skip to content

Commit 26a03dc

Browse files
committed
Add PyObject_GenericHash() function
Also, make Py_HashPointer() available in the limited C API.
1 parent cdf0abe commit 26a03dc

5 files changed

Lines changed: 50 additions & 7 deletions

File tree

‎docs/api.rst‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,9 @@ Python 3.13
465465

466466
See `Py_HashPointer() documentation <https://docs.python.org/dev/c-api/hash.html#c.Py_HashPointer>`__.
467467

468-
Not available on the limited C API.
468+
.. c:function:: Py_hash_t PyObject_GenericHash(PyObject *obj)
469+
470+
See `PyObject_GenericHash() documentation <https://docs.python.org/dev/c-api/hash.html#c.PyObject_GenericHash>`__.
469471

470472
.. c:type:: PyTime_t
471473

@@ -533,7 +535,6 @@ Not supported:
533535
* ``PyLong_AsNativeBytes()``
534536
* ``PyLong_FromNativeBytes()``
535537
* ``PyLong_FromUnsignedNativeBytes()``
536-
* ``PyObject_GenericHash()``.
537538
* ``PySys_Audit()``.
538539
* ``PySys_AuditTuple()``.
539540

‎docs/changelog.rst‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
Changelog
22
=========
33

4-
* 2026-10-10: Add ``PyUnicodeWriter_WriteUCS4()`` function.
4+
* 2026-10-10: Add ``PyObject_GenericHash()`` and
5+
``PyUnicodeWriter_WriteUCS4()`` functions.
56
* 2026-10-08: Add support for the limited C API 3.2.
67
* 2026-10-08: Add functions:
78

‎pythoncapi_compat.h‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,14 +1236,39 @@ PyDict_PopString(PyObject *dict, const char *key, PyObject **result)
12361236

12371237

12381238
// gh-111545 added Py_HashPointer() to Python 3.13
1239-
#if PY_VERSION_HEX < _Py_PACK_VERSION(3, 13) && !defined(Py_LIMITED_API)
1239+
#if PY_VERSION_HEX < _Py_PACK_VERSION(3, 13) || defined(Py_LIMITED_API)
12401240
static inline Py_hash_t Py_HashPointer(const void *ptr)
12411241
{
1242-
#if PY_VERSION_HEX >= _Py_PACK_VERSION(3, 9) && !defined(PYPY_VERSION)
1242+
#if PY_VERSION_HEX >= _Py_PACK_VERSION(3, 9) && !defined(PYPY_VERSION) && !defined(Py_LIMITED_API)
12431243
return _Py_HashPointer(ptr);
1244-
#else
1244+
#elif !defined(Py_LIMITED_API)
1245+
// PyPy _Py_HashPointer() argument type is "void*", not "const void*"
12451246
return _Py_HashPointer(_Py_CAST(void*, ptr));
1247+
#else
1248+
// Limited C API implementation
1249+
uintptr_t x = (uintptr_t)ptr;
1250+
Py_BUILD_ASSERT(sizeof(x) == sizeof(ptr));
1251+
1252+
// Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right
1253+
// to avoid excessive hash collisions for dicts and sets.
1254+
x = (x >> 4) | (x << (8 * sizeof(uintptr_t) - 4));
1255+
1256+
if (x == (uintptr_t)-1) {
1257+
x = (uintptr_t)-2;
1258+
}
1259+
1260+
Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t));
1261+
return (Py_hash_t)x;
1262+
#endif
1263+
}
12461264
#endif
1265+
1266+
1267+
#if PY_VERSION_HEX < _Py_PACK_VERSION(3, 13) || defined(Py_LIMITED_API)
1268+
Py_hash_t
1269+
PyObject_GenericHash(PyObject *obj)
1270+
{
1271+
return Py_HashPointer(obj);
12471272
}
12481273
#endif
12491274

‎tests/test_pythoncapi_compat.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def run_tests(module_name, std):
173173
except AttributeError:
174174
pass
175175
else:
176-
if attr in ("PY_VERSION_HEX", "PYPY_VERSION_NUM"):
176+
if attr in ('PY_VERSION_HEX', 'Py_LIMITED_API', 'PYPY_VERSION_NUM'):
177177
value = "0x%x" % value
178178
print("%s: %s" % (attr, value))
179179
empty_line = True

‎tests/test_pythoncapi_compat_cext.c‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,22 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
17621762
Py_DECREF(abc);
17631763
}
17641764

1765+
// Test PyObject_GenericHash()
1766+
{
1767+
PyObject *object_type = PyImport_ImportModuleAttrString("builtins", "object");
1768+
if (object_type == NULL) {
1769+
return NULL;
1770+
}
1771+
PyObject *object = PyObject_CallNoArgs(object_type);
1772+
Py_DECREF(object_type);
1773+
if (object == NULL) {
1774+
return NULL;
1775+
}
1776+
1777+
assert(PyObject_GenericHash(object) == Py_HashPointer(object));
1778+
Py_DECREF(object);
1779+
}
1780+
17651781
Py_RETURN_NONE;
17661782
}
17671783

0 commit comments

Comments
 (0)