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
C API: Add PyObject_GenericHash() function #113024
Just added Py_HashPointer() function can be used to implement the default Python object hashing function (object.__hash__()), but only in CPython. In other Python implementations the default object hash can depend not on the object address, but on its identity.
I think that we need a new function, PyObject_GenericHash() (similar to PyObject_GenericGetAttr() etc), that does not depend on CPython implementation details.
Just added Py_HashPointer() function can be used to implement the default Python object hashing function (object.hash()), but only in CPython. In other Python implementations the default object hash can depend not on the object address, but on its identity.
What is an object identity? The id() function? How is it computed? Is it different than the object address? Which Python implementations are you talking about?
If Py_HashPointer() should not be used to write "portable" C extensions, maybe only PyObject_GenericHash() should be used?
It is an entity that remains the same during the lifetime of the object and is unique for co-existing objects. The is operator is used to compare identities, a is b is true iff a and b is the same object. id() returns an integer representation of identity. In CPython it is the address of the object, but in other implementations (especially if the GC can move objects) it can be something other. For example in Jython it could not be an address. I don't know about PyPy.
Py_HashPointer() can still be used for hashing pointers to C functions or other external pointers, which are not managed by the Python GC.
We need also more portable C API for id() (and the reverse function?), but I am not sure about name and interface yet.
According to what you wrote, using Py_HashPointer() here is not portable and a bad idea. PyObject_GenericHash() should be used instead.
PyObject_GenericHash(obj) seems to be more complicated that hash(id(obj)): depending on how id() is implemented, a different hash function should be used:
CPython implements id() by returning the object memory address. PyObject_GenericHash(PyObject *obj) is just Py_HashPointer(obj).
PyPy has a more complicated id() implementation, and using Py_HashPointer() on id() may not be the best hash funciton to reduce the risk of hash collision.
IMO, in current API, PyObject*is the identity a Python object. You can dereference it. You can compare it with C ==.
Implementations where object identity is decoupled from the address can't really use PyObject* at all.
So, it seems to me that this should be something like PyObject_GenericHash(PyRef), with a HPy-like “reference” type. It's not something we have in current CPython.
Or is there an implementation where PyObject_GenericHash(PyObject) would be useful?
So, it seems to me that this should be something like PyObject_GenericHash(PyRef), with a HPy-like “reference” type. It's not something we have in current CPython.
In HPy, a reference is just a PyObject* in the "CPython build mode".
Or is there an implementation where PyObject_GenericHash(PyObject) would be useful?
To replace the private _Py_HashDouble() function, it looks useful if you care about other Python implementations than CPython, if I understood correctly.
In HPy, a reference is just a PyObject* in the "CPython build mode".
No, it still has reference semantics. You cannot compare HPy references with ==, period. In non-debug “CPython build mode” it'll work, but that's an unfortunate implementation detail.
Answering to @vstinner's suggestion on the PR (to not lose it in review comments). Note that it is not directly depending on id(). I just do not know how to say it better than "only depends on the object's identity". Having the same identity is not the same as having the same id. Even if two objects in different time can have the same id, their hashes can be different. The implementation that generates a random hash and preserves it during the lifetime of the object will be good too.
That's the thing, it's not. The address of the object can be changed during its lifetime in other Python implementations. This is why PyObject_GenericHash() is introduce at first place.
id() does not return the object's identity. It returns an integer that only depends on the object's identity and is unique for all co-existing objects. PyObject_GenericHash() also only depends on the object's identity, but without the uniqueness condition. There is no direct relation between them.
Activity
pythongh-113024: C API: Add PyObject_GenericHash() function
vstinner commentedon Dec 13, 2023
What is an object identity? The id() function? How is it computed? Is it different than the object address? Which Python implementations are you talking about?
If Py_HashPointer() should not be used to write "portable" C extensions, maybe only PyObject_GenericHash() should be used?
serhiy-storchaka commentedon Dec 13, 2023
It is an entity that remains the same during the lifetime of the object and is unique for co-existing objects. The
isoperator is used to compare identities,a is bis true iffaandbis the same object.id()returns an integer representation of identity. In CPython it is the address of the object, but in other implementations (especially if the GC can move objects) it can be something other. For example in Jython it could not be an address. I don't know about PyPy.Py_HashPointer()can still be used for hashing pointers to C functions or other external pointers, which are not managed by the Python GC.We need also more portable C API for
id()(and the reverse function?), but I am not sure about name and interface yet.vstinner commentedon Dec 14, 2023
In PR #112449, it was proposed to replace calls to the private
_Py_HashDouble(obj, value)function with:According to what you wrote, using
Py_HashPointer()here is not portable and a bad idea.PyObject_GenericHash()should be used instead.PyObject_GenericHash(obj)seems to be more complicated thathash(id(obj)): depending on howid()is implemented, a different hash function should be used:id()by returning the object memory address.PyObject_GenericHash(PyObject *obj)is justPy_HashPointer(obj).id()implementation, and usingPy_HashPointer()onid()may not be the best hash funciton to reduce the risk of hash collision.encukou commentedon Dec 18, 2023
IMO, in current API,
PyObject*is the identity a Python object. You can dereference it. You can compare it with C==.Implementations where object identity is decoupled from the address can't really use
PyObject*at all.So, it seems to me that this should be something like
PyObject_GenericHash(PyRef), with a HPy-like “reference” type. It's not something we have in current CPython.Or is there an implementation where
PyObject_GenericHash(PyObject)would be useful?vstinner commentedon Dec 18, 2023
In HPy, a reference is just a
PyObject*in the "CPython build mode".To replace the private
_Py_HashDouble()function, it looks useful if you care about other Python implementations than CPython, if I understood correctly.encukou commentedon Dec 18, 2023
No, it still has reference semantics. You cannot compare HPy references with
==, period. In non-debug “CPython build mode” it'll work, but that's an unfortunate implementation detail.serhiy-storchaka commentedon Dec 28, 2023
Proposition for the C API WG: capi-workgroup/decisions#5.
serhiy-storchaka commentedon Mar 18, 2024
Answering to @vstinner's suggestion on the PR (to not lose it in review comments). Note that it is not directly depending on
id(). I just do not know how to say it better than "only depends on the object's identity". Having the same identity is not the same as having the same id. Even if two objects in different time can have the same id, their hashes can be different. The implementation that generates a random hash and preserves it during the lifetime of the object will be good too.vstinner commentedon Mar 18, 2024
IMO you should be more specific and say that the hash value only depends on the memory address.
serhiy-storchaka commentedon Mar 18, 2024
That's the thing, it's not. The address of the object can be changed during its lifetime in other Python implementations. This is why
PyObject_GenericHash()is introduce at first place.id()does not return the object's identity. It returns an integer that only depends on the object's identity and is unique for all co-existing objects.PyObject_GenericHash()also only depends on the object's identity, but without the uniqueness condition. There is no direct relation between them.gh-113024: C API: Add PyObject_GenericHash() function (GH-113025)
pythongh-113024: C API: Add PyObject_GenericHash() function (pythonGH…
Squashed commit of the following:
Squashed commit of the following:
pythongh-113024: C API: Add PyObject_GenericHash() function (pythonGH…