gh-135228: Create __dict__ and __weakref__ descriptors for object (GH-136966)

This partially reverts #137047, keeping the tests for GC collectability of the
original class that dataclass adds `__slots__` to.
The reference leaks solved there are instead solved by having the `__dict__` &
`__weakref__` descriptors not tied to (and referencing) their class.

Instead, they're shared between all classes that need them (within
an interpreter).
The `__objclass__` ol the descriptors is set to `object`, since these
descriptors work with *any* object. (The appropriate checks were already
made in the get/set code, so the `__objclass__` check was redundant.)

The repr of these descriptors (and any others whose `__objclass__` is `object`)
now doesn't mention the objclass.

This change required adjustment of introspection code that checks
`__objclass__` to determine an object's “own” (i.e. not inherited) `__dict__`.
Third-party code that does similar introspection of the internals will also
need adjusting.


Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
Petr Viktorin 2025-08-18 14:25:51 +02:00 committed by GitHub
parent 92be979f64
commit 7dfa048bbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 171 additions and 120 deletions

View file

@ -1793,37 +1793,6 @@ sys__baserepl(PyObject *module, PyObject *Py_UNUSED(ignored))
return sys__baserepl_impl(module);
}
PyDoc_STRVAR(sys__clear_type_descriptors__doc__,
"_clear_type_descriptors($module, type, /)\n"
"--\n"
"\n"
"Private function for clearing certain descriptors from a type\'s dictionary.\n"
"\n"
"See gh-135228 for context.");
#define SYS__CLEAR_TYPE_DESCRIPTORS_METHODDEF \
{"_clear_type_descriptors", (PyCFunction)sys__clear_type_descriptors, METH_O, sys__clear_type_descriptors__doc__},
static PyObject *
sys__clear_type_descriptors_impl(PyObject *module, PyObject *type);
static PyObject *
sys__clear_type_descriptors(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
PyObject *type;
if (!PyObject_TypeCheck(arg, &PyType_Type)) {
_PyArg_BadArgument("_clear_type_descriptors", "argument", (&PyType_Type)->tp_name, arg);
goto exit;
}
type = arg;
return_value = sys__clear_type_descriptors_impl(module, type);
exit:
return return_value;
}
PyDoc_STRVAR(sys__is_gil_enabled__doc__,
"_is_gil_enabled($module, /)\n"
"--\n"
@ -1979,4 +1948,4 @@ exit:
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
/*[clinic end generated code: output=9052f399f40ca32d input=a9049054013a1b77]*/
/*[clinic end generated code: output=449d16326e69dcf6 input=a9049054013a1b77]*/

View file

@ -1906,6 +1906,7 @@ finalize_interp_clear(PyThreadState *tstate)
_PyXI_Fini(tstate->interp);
_PyExc_ClearExceptionGroupType(tstate->interp);
_Py_clear_generic_types(tstate->interp);
_PyTypes_FiniCachedDescriptors(tstate->interp);
/* Clear interpreter state and all thread states */
_PyInterpreterState_Clear(tstate);

View file

@ -2644,46 +2644,6 @@ sys__baserepl_impl(PyObject *module)
Py_RETURN_NONE;
}
/*[clinic input]
sys._clear_type_descriptors
type: object(subclass_of='&PyType_Type')
/
Private function for clearing certain descriptors from a type's dictionary.
See gh-135228 for context.
[clinic start generated code]*/
static PyObject *
sys__clear_type_descriptors_impl(PyObject *module, PyObject *type)
/*[clinic end generated code: output=5ad17851b762b6d9 input=dc536c97fde07251]*/
{
PyTypeObject *typeobj = (PyTypeObject *)type;
if (_PyType_HasFeature(typeobj, Py_TPFLAGS_IMMUTABLETYPE)) {
PyErr_SetString(PyExc_TypeError, "argument is immutable");
return NULL;
}
PyObject *dict = _PyType_GetDict(typeobj);
PyObject *dunder_dict = NULL;
if (PyDict_Pop(dict, &_Py_ID(__dict__), &dunder_dict) < 0) {
return NULL;
}
PyObject *dunder_weakref = NULL;
if (PyDict_Pop(dict, &_Py_ID(__weakref__), &dunder_weakref) < 0) {
PyType_Modified(typeobj);
Py_XDECREF(dunder_dict);
return NULL;
}
PyType_Modified(typeobj);
// We try to hold onto a reference to these until after we call
// PyType_Modified(), in case their deallocation triggers somer user code
// that tries to do something to the type.
Py_XDECREF(dunder_dict);
Py_XDECREF(dunder_weakref);
Py_RETURN_NONE;
}
/*[clinic input]
sys._is_gil_enabled -> bool
@ -2881,7 +2841,6 @@ static PyMethodDef sys_methods[] = {
SYS__STATS_DUMP_METHODDEF
#endif
SYS__GET_CPU_COUNT_CONFIG_METHODDEF
SYS__CLEAR_TYPE_DESCRIPTORS_METHODDEF
SYS__IS_GIL_ENABLED_METHODDEF
SYS__DUMP_TRACELETS_METHODDEF
{NULL, NULL} // sentinel