_Py_HashBytes in 3.12 vs 3.13

29 views
Skip to first unread message

David Lawson

unread,
Jun 5, 2025, 11:26:47 AMJun 5
to cython-users
Hi,

I'm trying to make some make a library work in cPython 3.13 but have hit an issue I don't understand. It's possibly more to do with cPython than cython but I'm seeing a difference between 3.12 and 3.13.

Consider the following:

cython_hash.pyx:
```
cdef extern from "Python.h":
    cdef Py_hash_t _Py_HashBytes(const void *src, Py_ssize_t len)


cpdef long strhash(bytes a, long start, long size):
    cdef const unsigned char* p = a
    p += start
    return _Py_HashBytes(p, size)
```

setup.py
```
from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize(['cython_hash.pyx']),
    packages=['cython_hash'],
)
```

Building for both using:
```
$ ~/.pyenv/versions/cython_test-3.12.8/bin/python setup.py build_ext --inplace
$ ~/.pyenv/versions/cython_test-3.13.4/bin/python setup.py build_ext --inplace
```

and then:
```
$ PYTHONHASHSEED=0 ~/.pyenv/versions/cython_test-3.12.8/bin/python -c "import cython_hash; print(cython_hash.strhash(b'hello', 0, 5)); print(hash(b'hello'))"
-2096571579003691106
-2096571579003691106
$ PYTHONHASHSEED=0 ~/.pyenv/versions/cython_test-3.13.4/bin/python -c "import cython_hash; print(cython_hash.strhash(b'hello', 0, 5)); print(hash(b'hello'))"
-884072546
-2096571579003691106
```

Why is `_Py_HashBytes` giving a different value in 3.13?

I have looked at the cPython source for both 3.12 and 3.13 and have observed some changes around this but nothing seemed to actually switch out the implementation of it, plus hash(b'hello') gives the same result as 3.12.

Many thanks,

David

da-woods

unread,
Jun 8, 2025, 3:19:25 AMJun 8
to cython...@googlegroups.com
Hi David,

I think it was hidden in Py3.13 (https://github.com/python/cpython/pull/107026) as private API. You're probably getting a C warning about "implicit declaration" which you're ignoring.  C assumes that any function with an "implicit declaration" returns an int, which'll be smaller than a Py_hash_t, hence the different results.

It re-appears in Py3.14 as the public "Py_HashBuffer" (but that's no use to you for now)

In the short-term you need to tell C (rather than Cython) about the actual signature and it should work:

```
cdef extern from "Python.h":
    """
    Py_hash_t _Py_HashBytes(const void *src, Py_ssize_t len); /* prototype */
    """
    cdef Py_hash_t _Py_HashBytes(const void *src, Py_ssize_t len)
```

David
--

---
You received this message because you are subscribed to the Google Groups "cython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cython-users...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/cython-users/cfc449ed-5259-4c9c-a6a9-c01b0a99c731n%40googlegroups.com.


Reply all
Reply to author
Forward
0 new messages