mirror of https://github.com/python/cpython
gh-94673: Add Per-Interpreter tp_subclasses for Static Builtin Types (gh-95301)
This commit is contained in:
parent
e1182bc377
commit
87154d8dd8
|
@ -135,7 +135,7 @@ Quick Reference
|
||||||
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
||||||
| [:c:member:`~PyTypeObject.tp_cache`] | :c:type:`PyObject` * | | | | |
|
| [:c:member:`~PyTypeObject.tp_cache`] | :c:type:`PyObject` * | | | | |
|
||||||
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
||||||
| [:c:member:`~PyTypeObject.tp_subclasses`] | :c:type:`PyObject` * | __subclasses__ | | | |
|
| [:c:member:`~PyTypeObject.tp_subclasses`] | void * | __subclasses__ | | | |
|
||||||
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
||||||
| [:c:member:`~PyTypeObject.tp_weaklist`] | :c:type:`PyObject` * | | | | |
|
| [:c:member:`~PyTypeObject.tp_weaklist`] | :c:type:`PyObject` * | | | | |
|
||||||
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
|
||||||
|
@ -1934,9 +1934,17 @@ and :c:type:`PyType_Type` effectively act as defaults.)
|
||||||
This field is not inherited.
|
This field is not inherited.
|
||||||
|
|
||||||
|
|
||||||
.. c:member:: PyObject* PyTypeObject.tp_subclasses
|
.. c:member:: void* PyTypeObject.tp_subclasses
|
||||||
|
|
||||||
List of weak references to subclasses. Internal use only.
|
A collection of subclasses. Internal use only. May be an invalid pointer.
|
||||||
|
|
||||||
|
To get a list of subclasses, call the Python method
|
||||||
|
:py:meth:`~class.__subclasses__`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.12
|
||||||
|
|
||||||
|
For some types, this field does not hold a valid :c:expr:`PyObject*`.
|
||||||
|
The type was changed to :c:expr:`void*` to indicate this.
|
||||||
|
|
||||||
**Inheritance:**
|
**Inheritance:**
|
||||||
|
|
||||||
|
|
|
@ -440,6 +440,16 @@ Porting to Python 3.12
|
||||||
using the existing public C-API instead, or, if necessary, the
|
using the existing public C-API instead, or, if necessary, the
|
||||||
(internal-only) ``_PyObject_GET_WEAKREFS_LISTPTR()`` macro.
|
(internal-only) ``_PyObject_GET_WEAKREFS_LISTPTR()`` macro.
|
||||||
|
|
||||||
|
* This internal-only :c:member:`PyTypeObject.tp_subclasses` may now not be
|
||||||
|
a valid object pointer. Its type was changed to :c:expr:`void *` to
|
||||||
|
reflect this. We mention this in case someone happens to be accessing the
|
||||||
|
internal-only field directly.
|
||||||
|
|
||||||
|
To get a list of subclasses, call the Python method
|
||||||
|
:py:meth:`~class.__subclasses__` (using :c:func:`PyObject_CallMethod`,
|
||||||
|
for example).
|
||||||
|
|
||||||
|
|
||||||
Deprecated
|
Deprecated
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -217,8 +217,8 @@ struct _typeobject {
|
||||||
inquiry tp_is_gc; /* For PyObject_IS_GC */
|
inquiry tp_is_gc; /* For PyObject_IS_GC */
|
||||||
PyObject *tp_bases;
|
PyObject *tp_bases;
|
||||||
PyObject *tp_mro; /* method resolution order */
|
PyObject *tp_mro; /* method resolution order */
|
||||||
PyObject *tp_cache;
|
PyObject *tp_cache; /* no longer used */
|
||||||
PyObject *tp_subclasses;
|
void *tp_subclasses; /* for static builtin types this is an index */
|
||||||
PyObject *tp_weaklist; /* not used for static builtin types */
|
PyObject *tp_weaklist; /* not used for static builtin types */
|
||||||
destructor tp_del;
|
destructor tp_del;
|
||||||
|
|
||||||
|
@ -227,7 +227,6 @@ struct _typeobject {
|
||||||
|
|
||||||
destructor tp_finalize;
|
destructor tp_finalize;
|
||||||
vectorcallfunc tp_vectorcall;
|
vectorcallfunc tp_vectorcall;
|
||||||
size_t tp_static_builtin_index; /* 0 means "not initialized" */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This struct is used by the specializer
|
/* This struct is used by the specializer
|
||||||
|
|
|
@ -351,6 +351,7 @@ _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values)
|
||||||
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
|
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
|
||||||
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);
|
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);
|
||||||
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
|
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
|
||||||
|
extern int _PyType_HasSubclasses(PyTypeObject *);
|
||||||
extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
|
extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
|
||||||
|
|
||||||
// Access macro to the members which are floating "behind" the object
|
// Access macro to the members which are floating "behind" the object
|
||||||
|
|
|
@ -45,6 +45,7 @@ struct type_cache {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyTypeObject *type;
|
PyTypeObject *type;
|
||||||
|
PyObject *tp_subclasses;
|
||||||
/* We never clean up weakrefs for static builtin types since
|
/* We never clean up weakrefs for static builtin types since
|
||||||
they will effectively never get triggered. However, there
|
they will effectively never get triggered. However, there
|
||||||
are also some diagnostic uses for the list of weakrefs,
|
are also some diagnostic uses for the list of weakrefs,
|
||||||
|
|
|
@ -1507,7 +1507,7 @@ class SizeofTest(unittest.TestCase):
|
||||||
check((1,2,3), vsize('') + 3*self.P)
|
check((1,2,3), vsize('') + 3*self.P)
|
||||||
# type
|
# type
|
||||||
# static type: PyTypeObject
|
# static type: PyTypeObject
|
||||||
fmt = 'P2nPI13Pl4Pn9Pn12PIPI'
|
fmt = 'P2nPI13Pl4Pn9Pn12PIP'
|
||||||
s = vsize('2P' + fmt)
|
s = vsize('2P' + fmt)
|
||||||
check(int, s)
|
check(int, s)
|
||||||
# class
|
# class
|
||||||
|
|
|
@ -580,7 +580,7 @@ _PyStructSequence_FiniType(PyTypeObject *type)
|
||||||
assert(type->tp_base == &PyTuple_Type);
|
assert(type->tp_base == &PyTuple_Type);
|
||||||
|
|
||||||
// Cannot delete a type if it still has subclasses
|
// Cannot delete a type if it still has subclasses
|
||||||
if (type->tp_subclasses != NULL) {
|
if (_PyType_HasSubclasses(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ static inline PyTypeObject * subclass_from_ref(PyObject *ref);
|
||||||
static inline int
|
static inline int
|
||||||
static_builtin_index_is_set(PyTypeObject *self)
|
static_builtin_index_is_set(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
return self->tp_static_builtin_index > 0;
|
return self->tp_subclasses != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t
|
static inline size_t
|
||||||
|
@ -81,7 +81,7 @@ static_builtin_index_get(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
assert(static_builtin_index_is_set(self));
|
assert(static_builtin_index_is_set(self));
|
||||||
/* We store a 1-based index so 0 can mean "not initialized". */
|
/* We store a 1-based index so 0 can mean "not initialized". */
|
||||||
return self->tp_static_builtin_index - 1;
|
return (size_t)self->tp_subclasses - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
|
@ -89,13 +89,13 @@ static_builtin_index_set(PyTypeObject *self, size_t index)
|
||||||
{
|
{
|
||||||
assert(index < _Py_MAX_STATIC_BUILTIN_TYPES);
|
assert(index < _Py_MAX_STATIC_BUILTIN_TYPES);
|
||||||
/* We store a 1-based index so 0 can mean "not initialized". */
|
/* We store a 1-based index so 0 can mean "not initialized". */
|
||||||
self->tp_static_builtin_index = index + 1;
|
self->tp_subclasses = (PyObject *)(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
static_builtin_index_clear(PyTypeObject *self)
|
static_builtin_index_clear(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
self->tp_static_builtin_index = 0;
|
self->tp_subclasses = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline static_builtin_state *
|
static inline static_builtin_state *
|
||||||
|
@ -127,6 +127,7 @@ static_builtin_state_init(PyTypeObject *self)
|
||||||
|
|
||||||
static_builtin_state *state = static_builtin_state_get(interp, self);
|
static_builtin_state *state = static_builtin_state_get(interp, self);
|
||||||
state->type = self;
|
state->type = self;
|
||||||
|
/* state->tp_subclasses is left NULL until init_subclasses() sets it. */
|
||||||
/* state->tp_weaklist is left NULL until insert_head() or insert_after()
|
/* state->tp_weaklist is left NULL until insert_head() or insert_after()
|
||||||
(in weakrefobject.c) sets it. */
|
(in weakrefobject.c) sets it. */
|
||||||
}
|
}
|
||||||
|
@ -373,6 +374,8 @@ _PyTypes_Fini(PyInterpreterState *interp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject * lookup_subclasses(PyTypeObject *);
|
||||||
|
|
||||||
void
|
void
|
||||||
PyType_Modified(PyTypeObject *type)
|
PyType_Modified(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
@ -395,7 +398,7 @@ PyType_Modified(PyTypeObject *type)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *subclasses = type->tp_subclasses;
|
PyObject *subclasses = lookup_subclasses(type);
|
||||||
if (subclasses != NULL) {
|
if (subclasses != NULL) {
|
||||||
assert(PyDict_CheckExact(subclasses));
|
assert(PyDict_CheckExact(subclasses));
|
||||||
|
|
||||||
|
@ -783,7 +786,7 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
|
||||||
Py_XDECREF(old_mro);
|
Py_XDECREF(old_mro);
|
||||||
|
|
||||||
// Avoid creating an empty list if there is no subclass
|
// Avoid creating an empty list if there is no subclass
|
||||||
if (type->tp_subclasses != NULL) {
|
if (_PyType_HasSubclasses(type)) {
|
||||||
/* Obtain a copy of subclasses list to iterate over.
|
/* Obtain a copy of subclasses list to iterate over.
|
||||||
|
|
||||||
Otherwise type->tp_subclasses might be altered
|
Otherwise type->tp_subclasses might be altered
|
||||||
|
@ -4345,10 +4348,13 @@ type_dealloc_common(PyTypeObject *type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void clear_subclasses(PyTypeObject *self);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clear_static_tp_subclasses(PyTypeObject *type)
|
clear_static_tp_subclasses(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
if (type->tp_subclasses == NULL) {
|
PyObject *subclasses = lookup_subclasses(type);
|
||||||
|
if (subclasses == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4372,9 +4378,19 @@ clear_static_tp_subclasses(PyTypeObject *type)
|
||||||
going to leak. This mostly only affects embedding scenarios.
|
going to leak. This mostly only affects embedding scenarios.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// For now we just clear tp_subclasses.
|
// For now we just do a sanity check and then clear tp_subclasses.
|
||||||
|
Py_ssize_t i = 0;
|
||||||
|
PyObject *key, *ref; // borrowed ref
|
||||||
|
while (PyDict_Next(subclasses, &i, &key, &ref)) {
|
||||||
|
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
|
||||||
|
if (subclass == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// All static builtin subtypes should have been finalized already.
|
||||||
|
assert(!(subclass->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
|
||||||
|
}
|
||||||
|
|
||||||
Py_CLEAR(type->tp_subclasses);
|
clear_subclasses(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -4424,7 +4440,7 @@ type_dealloc(PyTypeObject *type)
|
||||||
Py_XDECREF(type->tp_bases);
|
Py_XDECREF(type->tp_bases);
|
||||||
Py_XDECREF(type->tp_mro);
|
Py_XDECREF(type->tp_mro);
|
||||||
Py_XDECREF(type->tp_cache);
|
Py_XDECREF(type->tp_cache);
|
||||||
Py_XDECREF(type->tp_subclasses);
|
clear_subclasses(type);
|
||||||
|
|
||||||
/* A type's tp_doc is heap allocated, unlike the tp_doc slots
|
/* A type's tp_doc is heap allocated, unlike the tp_doc slots
|
||||||
* of most other objects. It's okay to cast it to char *.
|
* of most other objects. It's okay to cast it to char *.
|
||||||
|
@ -4444,6 +4460,30 @@ type_dealloc(PyTypeObject *type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
lookup_subclasses(PyTypeObject *self)
|
||||||
|
{
|
||||||
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
|
static_builtin_state *state = _PyStaticType_GetState(self);
|
||||||
|
assert(state != NULL);
|
||||||
|
return state->tp_subclasses;
|
||||||
|
}
|
||||||
|
return (PyObject *)self->tp_subclasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyType_HasSubclasses(PyTypeObject *self)
|
||||||
|
{
|
||||||
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN &&
|
||||||
|
_PyStaticType_GetState(self) == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lookup_subclasses(self) == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject*
|
PyObject*
|
||||||
_PyType_GetSubclasses(PyTypeObject *self)
|
_PyType_GetSubclasses(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
|
@ -4452,7 +4492,7 @@ _PyType_GetSubclasses(PyTypeObject *self)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *subclasses = self->tp_subclasses; // borrowed ref
|
PyObject *subclasses = lookup_subclasses(self); // borrowed ref
|
||||||
if (subclasses == NULL) {
|
if (subclasses == NULL) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -6830,6 +6870,36 @@ _PyStaticType_InitBuiltin(PyTypeObject *self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
init_subclasses(PyTypeObject *self)
|
||||||
|
{
|
||||||
|
PyObject *subclasses = PyDict_New();
|
||||||
|
if (subclasses == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
|
static_builtin_state *state = _PyStaticType_GetState(self);
|
||||||
|
state->tp_subclasses = subclasses;
|
||||||
|
return subclasses;
|
||||||
|
}
|
||||||
|
self->tp_subclasses = (void *)subclasses;
|
||||||
|
return subclasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clear_subclasses(PyTypeObject *self)
|
||||||
|
{
|
||||||
|
/* Delete the dictionary to save memory. _PyStaticType_Dealloc()
|
||||||
|
callers also test if tp_subclasses is NULL to check if a static type
|
||||||
|
has no subclass. */
|
||||||
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
|
static_builtin_state *state = _PyStaticType_GetState(self);
|
||||||
|
Py_CLEAR(state->tp_subclasses);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Py_CLEAR(self->tp_subclasses);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
add_subclass(PyTypeObject *base, PyTypeObject *type)
|
add_subclass(PyTypeObject *base, PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
@ -6846,9 +6916,9 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
|
||||||
// Only get tp_subclasses after creating the key and value.
|
// Only get tp_subclasses after creating the key and value.
|
||||||
// PyWeakref_NewRef() can trigger a garbage collection which can execute
|
// PyWeakref_NewRef() can trigger a garbage collection which can execute
|
||||||
// arbitrary Python code and so modify base->tp_subclasses.
|
// arbitrary Python code and so modify base->tp_subclasses.
|
||||||
PyObject *subclasses = base->tp_subclasses;
|
PyObject *subclasses = lookup_subclasses(base);
|
||||||
if (subclasses == NULL) {
|
if (subclasses == NULL) {
|
||||||
base->tp_subclasses = subclasses = PyDict_New();
|
subclasses = init_subclasses(base);
|
||||||
if (subclasses == NULL) {
|
if (subclasses == NULL) {
|
||||||
Py_DECREF(key);
|
Py_DECREF(key);
|
||||||
Py_DECREF(ref);
|
Py_DECREF(ref);
|
||||||
|
@ -6905,10 +6975,13 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
|
||||||
We fall back to manually traversing the values. */
|
We fall back to manually traversing the values. */
|
||||||
Py_ssize_t i = 0;
|
Py_ssize_t i = 0;
|
||||||
PyObject *ref; // borrowed ref
|
PyObject *ref; // borrowed ref
|
||||||
while (PyDict_Next((PyObject *)base->tp_subclasses, &i, &key, &ref)) {
|
PyObject *subclasses = lookup_subclasses(base);
|
||||||
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
|
if (subclasses != NULL) {
|
||||||
if (subclass == type) {
|
while (PyDict_Next(subclasses, &i, &key, &ref)) {
|
||||||
return Py_NewRef(key);
|
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
|
||||||
|
if (subclass == type) {
|
||||||
|
return Py_NewRef(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* It wasn't found. */
|
/* It wasn't found. */
|
||||||
|
@ -6918,7 +6991,7 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
|
||||||
static void
|
static void
|
||||||
remove_subclass(PyTypeObject *base, PyTypeObject *type)
|
remove_subclass(PyTypeObject *base, PyTypeObject *type)
|
||||||
{
|
{
|
||||||
PyObject *subclasses = base->tp_subclasses; // borrowed ref
|
PyObject *subclasses = lookup_subclasses(base); // borrowed ref
|
||||||
if (subclasses == NULL) {
|
if (subclasses == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -6934,10 +7007,7 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
|
||||||
Py_XDECREF(key);
|
Py_XDECREF(key);
|
||||||
|
|
||||||
if (PyDict_Size(subclasses) == 0) {
|
if (PyDict_Size(subclasses) == 0) {
|
||||||
// Delete the dictionary to save memory. _PyStaticType_Dealloc()
|
clear_subclasses(base);
|
||||||
// callers also test if tp_subclasses is NULL to check if a static type
|
|
||||||
// has no subclass.
|
|
||||||
Py_CLEAR(base->tp_subclasses);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9022,7 +9092,7 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
|
||||||
// It is safe to use a borrowed reference because update_subclasses() is
|
// It is safe to use a borrowed reference because update_subclasses() is
|
||||||
// only used with update_slots_callback() which doesn't modify
|
// only used with update_slots_callback() which doesn't modify
|
||||||
// tp_subclasses.
|
// tp_subclasses.
|
||||||
PyObject *subclasses = type->tp_subclasses; // borrowed ref
|
PyObject *subclasses = lookup_subclasses(type); // borrowed ref
|
||||||
if (subclasses == NULL) {
|
if (subclasses == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue