Skip to content

gh-135228: Create __dict__ and __weakref__ descriptors for object #136966

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Share __dict__ and __weakref__ descriptors
  • Loading branch information
encukou committed Jul 30, 2025
commit d0d2f73566699e2c0114fcd1466e023b53e01b77
7 changes: 7 additions & 0 deletions Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,13 @@ struct _Py_interp_cached_objects {
PyTypeObject *paramspecargs_type;
PyTypeObject *paramspeckwargs_type;
PyTypeObject *constevaluator_type;

/* Descriptors for __dict__ and __weakref__ */
#ifdef Py_GIL_DISABLED
PyMutex descriptor_mutex;
#endif
PyObject *dict_descriptor;
PyObject *weakref_descriptor;
};

struct _Py_interp_static_objects {
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ extern void _PyTypes_FiniTypes(PyInterpreterState *);
extern void _PyTypes_FiniExtTypes(PyInterpreterState *interp);
extern void _PyTypes_Fini(PyInterpreterState *);
extern void _PyTypes_AfterFork(void);
extern void _PyTypes_FiniCachedDescriptors(PyInterpreterState *);

static inline PyObject **
_PyStaticType_GET_WEAKREFS_LISTPTR(managed_static_type_state *state)
Expand Down
114 changes: 69 additions & 45 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4036,29 +4036,15 @@ subtype_getweakref(PyObject *obj, void *context)
return Py_NewRef(result);
}

/* Three variants on the subtype_getsets list. */

static char subtype_getset_dict_name[] = "__dict__";
static char subtype_getset_weakref_name[] = "__weakref__";

static PyGetSetDef subtype_getsets_full[] = {
{subtype_getset_dict_name, subtype_dict, subtype_setdict,
PyDoc_STR("dictionary for instance variables")},
{subtype_getset_weakref_name, subtype_getweakref, NULL,
PyDoc_STR("list of weak references to the object")},
{0}
/* getset definitions for common descriptors */
static PyGetSetDef subtype_getset_dict = {
"__dict__", subtype_dict, subtype_setdict,
PyDoc_STR("dictionary for instance variables"),
};

static PyGetSetDef subtype_getsets_dict_only[] = {
{subtype_getset_dict_name, subtype_dict, subtype_setdict,
PyDoc_STR("dictionary for instance variables")},
{0}
};

static PyGetSetDef subtype_getsets_weakref_only[] = {
{subtype_getset_weakref_name, subtype_getweakref, NULL,
PyDoc_STR("list of weak references to the object")},
{0}
static PyGetSetDef subtype_getset_weakref = {
"__weakref__", subtype_getweakref, NULL,
PyDoc_STR("list of weak references to the object"),
};

static int
Expand Down Expand Up @@ -4594,10 +4580,36 @@ type_new_classmethod(PyObject *dict, PyObject *attr)
return 0;
}

/* Add __dict__ or __weakref__ descriptor */
static int
type_add_common_descriptor(PyInterpreterState *interp,
PyObject **cache,
PyGetSetDef *getset_def,
PyObject *dict)
{
#ifdef Py_GIL_DISABLED
PyMutex_Lock(&interp->cached_objects.descriptor_mutex);
#endif
PyObject *descr = *cache;
if (!descr) {
descr = PyDescr_NewGetSet(&PyBaseObject_Type, getset_def);
*cache = descr;
}
#ifdef Py_GIL_DISABLED
PyMutex_Unlock(&interp->cached_objects.descriptor_mutex);
#endif
if (!descr) {
return -1;
}
if (PyDict_SetDefaultRef(dict, PyDescr_NAME(descr), descr, NULL) < 0) {
return -1;
}
return 0;
}

/* Add descriptors for custom slots from __slots__, or for __dict__ */
static int
type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type)
type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type, PyObject *dict)
{
PyHeapTypeObject *et = (PyHeapTypeObject *)type;
Py_ssize_t slotoffset = ctx->base->tp_basicsize;
Expand Down Expand Up @@ -4635,25 +4647,38 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type)
type->tp_basicsize = slotoffset;
type->tp_itemsize = ctx->base->tp_itemsize;
type->tp_members = _PyHeapType_GET_MEMBERS(et);

PyInterpreterState *interp = _PyInterpreterState_GET();

if (type->tp_dictoffset) {
if (type_add_common_descriptor(
interp,
&interp->cached_objects.dict_descriptor,
&subtype_getset_dict,
dict) < 0)
{
return -1;
}
}
if (type->tp_weaklistoffset) {
if (type_add_common_descriptor(
interp,
&interp->cached_objects.weakref_descriptor,
&subtype_getset_weakref,
dict) < 0)
{
return -1;
}
}

return 0;
}


static void
type_new_set_slots(const type_new_ctx *ctx, PyTypeObject *type)
{
if (type->tp_weaklistoffset && type->tp_dictoffset) {
type->tp_getset = subtype_getsets_full;
}
else if (type->tp_weaklistoffset && !type->tp_dictoffset) {
type->tp_getset = subtype_getsets_weakref_only;
}
else if (!type->tp_weaklistoffset && type->tp_dictoffset) {
type->tp_getset = subtype_getsets_dict_only;
}
else {
type->tp_getset = NULL;
}
type->tp_getset = NULL;

/* Special case some slots */
if (type->tp_dictoffset != 0 || ctx->nslot > 0) {
Expand Down Expand Up @@ -4758,7 +4783,7 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type)
return -1;
}

if (type_new_descriptors(ctx, type) < 0) {
if (type_new_descriptors(ctx, type, dict) < 0) {
return -1;
}

Expand Down Expand Up @@ -6642,6 +6667,14 @@ _PyStaticType_FiniBuiltin(PyInterpreterState *interp, PyTypeObject *type)
}


void
_PyTypes_FiniCachedDescriptors(PyInterpreterState *interp)
{
Py_CLEAR(interp->cached_objects.dict_descriptor);
Py_CLEAR(interp->cached_objects.weakref_descriptor);
}


static void
type_dealloc(PyObject *self)
{
Expand Down Expand Up @@ -8332,16 +8365,7 @@ type_add_getset(PyTypeObject *type)

PyObject *dict = lookup_tp_dict(type);
for (; gsp->name != NULL; gsp++) {
PyTypeObject *descr_type = type;
// Hack: dict and weakref descriptors are created for `object`,
// rather than this specific type.
// We identify their PyGetSetDef by pointer equality on name.
if (gsp->name == subtype_getset_dict_name
|| gsp->name == subtype_getset_weakref_name)
{
descr_type = &PyBaseObject_Type;
}
PyObject *descr = PyDescr_NewGetSet(descr_type, gsp);
PyObject *descr = PyDescr_NewGetSet(type, gsp);
if (descr == NULL) {
return -1;
}
Expand Down
1 change: 1 addition & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy