Skip to content

Commit 4007869

Browse files
eendebakptclaude
andcommitted
gh-132042: Test subclassing a type whose tp_dict is a dict subclass
Add _testcapi.MyListDictSubclass, a static type like MyList whose tp_dict is initialized with a dict subclass before PyType_Ready(), as extensions may do, and check that subclasses created from Python still inherit its special methods. Co-Authored-By: Claude Fable 5.1 <[email protected]>
1 parent 8d742e0 commit 4007869

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

‎Lib/test/test_capi/test_type.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,19 @@ class MyList(list):
485485
self.assertFalse(type_issubtype(frozendict, dict))
486486
self.assertFalse(type_issubtype(MyList, tuple))
487487

488+
def test_subclass_type_with_dict_subclass_tp_dict(self):
489+
# gh-132042: MyListDictSubclass initializes tp_dict with a dict
490+
# subclass before PyType_Ready(), as extensions may do. Creating a
491+
# subclass must still find the special methods in that dict.
492+
class Sub(_testcapi.MyListDictSubclass):
493+
pass
494+
self.assertEqual(len(Sub([1, 2, 3])), 3)
495+
496+
class Sub2(_testcapi.MyListDictSubclass):
497+
def __len__(self):
498+
return 5
499+
self.assertEqual(len(Sub2([1, 2, 3])), 5)
500+
488501
def test_type_modified(self):
489502
# Test PyType_Modified()
490503
type_modified = _testlimitedcapi.type_modified

‎Modules/_testcapimodule.c‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,6 +3281,17 @@ static PyTypeObject MyList_Type = {
32813281
MyList_new, /* tp_new */
32823282
};
32833283

3284+
/* Like MyList, but tp_dict is initialized with a dict subclass before
3285+
PyType_Ready(), as extension modules may do (e.g. ctypes before 3.13). */
3286+
static PyTypeObject MyListDictSubclass_Type = {
3287+
PyVarObject_HEAD_INIT(NULL, 0)
3288+
"MyListDictSubclass",
3289+
sizeof(MyListObject),
3290+
.tp_dealloc = MyList_dealloc,
3291+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
3292+
.tp_new = MyList_new,
3293+
};
3294+
32843295
/* Test PEP 560 */
32853296

32863297
typedef struct {
@@ -3597,6 +3608,28 @@ _testcapi_exec(PyObject *m)
35973608
Py_INCREF(&MyList_Type);
35983609
PyModule_AddObject(m, "MyList", (PyObject *)&MyList_Type);
35993610

3611+
MyListDictSubclass_Type.tp_base = &PyList_Type;
3612+
if (MyListDictSubclass_Type.tp_dict == NULL) {
3613+
PyObject *cls = PyObject_CallFunction((PyObject *)&PyType_Type,
3614+
"s(O){}", "DictSubclass",
3615+
&PyDict_Type);
3616+
if (cls == NULL) {
3617+
return -1;
3618+
}
3619+
MyListDictSubclass_Type.tp_dict = PyObject_CallNoArgs(cls);
3620+
Py_DECREF(cls);
3621+
if (MyListDictSubclass_Type.tp_dict == NULL) {
3622+
return -1;
3623+
}
3624+
}
3625+
/* PyModule_AddType() would skip PyType_Ready() as tp_dict is set. */
3626+
if (PyType_Ready(&MyListDictSubclass_Type) < 0) {
3627+
return -1;
3628+
}
3629+
if (PyModule_AddType(m, &MyListDictSubclass_Type) < 0) {
3630+
return -1;
3631+
}
3632+
36003633
if (PyType_Ready(&GenericAlias_Type) < 0)
36013634
return -1;
36023635
Py_INCREF(&GenericAlias_Type);

0 commit comments

Comments
 (0)