From 5c294fb0e634afc4807ca83032ace356512c97dc Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 25 Sep 2001 03:43:42 +0000 Subject: Make __class__ assignment possible, when the object structures are the same. I hope the test for structural equivalence is stringent enough. It only allows the assignment if the old and new types: - have the same basic size - have the same item size - have the same dict offset - have the same weaklist offset - have the same GC flag bit - have a common base that is the same except for maybe the dict and weaklist (which may have been added separately at the same offsets in both types) --- Lib/test/test_descr.py | 28 ++++++++++++++++ Objects/typeobject.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 5bd837e..766c399 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -2005,6 +2005,33 @@ def descrdoc(): check(file.closed, "flag set if the file is closed") # getset descriptor check(file.name, "file name") # member descriptor +def setclass(): + if verbose: print "Testing __class__ assignment..." + class C(object): pass + class D(object): pass + class E(object): pass + class F(D, E): pass + for cls in C, D, E, F: + for cls2 in C, D, E, F: + x = cls() + x.__class__ = cls2 + verify(x.__class__ is cls2) + x.__class__ = cls + verify(x.__class__ is cls) + def cant(x, C): + try: + x.__class__ = C + except TypeError: + pass + else: + raise TestFailed, "shouldn't allow %r.__class__ = %r" % (x, C) + cant(C(), list) + cant(list(), C) + cant(C(), 1) + cant(C(), object) + cant(object(), list) + cant(list(), object) + def test_main(): lists() @@ -2047,6 +2074,7 @@ def test_main(): rich_comparisons() coercions() descrdoc() + setclass() if verbose: print "All OK" if __name__ == "__main__": diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 027a568..877a3bd 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1192,8 +1192,87 @@ object_free(PyObject *self) PyObject_Del(self); } -static PyMemberDef object_members[] = { - {"__class__", T_OBJECT, offsetof(PyObject, ob_type), READONLY}, +static PyObject * +object_get_class(PyObject *self, void *closure) +{ + Py_INCREF(self->ob_type); + return (PyObject *)(self->ob_type); +} + +static int +equiv_structs(PyTypeObject *a, PyTypeObject *b) +{ + return a == b || + (a != NULL && + b != NULL && + a->tp_basicsize == b->tp_basicsize && + a->tp_itemsize == b->tp_itemsize && + a->tp_dictoffset == b->tp_dictoffset && + a->tp_weaklistoffset == b->tp_weaklistoffset && + ((a->tp_flags & Py_TPFLAGS_HAVE_GC) == + (b->tp_flags & Py_TPFLAGS_HAVE_GC))); +} + +static int +same_slots_added(PyTypeObject *a, PyTypeObject *b) +{ + PyTypeObject *base = a->tp_base; + int size; + + if (base != b->tp_base) + return 0; + if (equiv_structs(a, base) && equiv_structs(b, base)) + return 1; + size = base->tp_basicsize; + if (a->tp_dictoffset == size && b->tp_dictoffset == size) + size += sizeof(PyObject *); + if (a->tp_weaklistoffset == size && b->tp_weaklistoffset == size) + size += sizeof(PyObject *); + return size == a->tp_basicsize && size == b->tp_basicsize; +} + +static int +object_set_class(PyObject *self, PyObject *value, void *closure) +{ + PyTypeObject *old = self->ob_type; + PyTypeObject *new, *newbase, *oldbase; + + if (!PyType_Check(value)) { + PyErr_Format(PyExc_TypeError, + "__class__ must be set to new-style class, not '%s' object", + value->ob_type->tp_name); + return -1; + } + new = (PyTypeObject *)value; + newbase = new; + oldbase = old; + while (equiv_structs(newbase, newbase->tp_base)) + newbase = newbase->tp_base; + while (equiv_structs(oldbase, oldbase->tp_base)) + oldbase = oldbase->tp_base; + if (newbase != oldbase && + (newbase->tp_base != oldbase->tp_base || + !same_slots_added(newbase, oldbase))) { + PyErr_Format(PyExc_TypeError, + "__class__ assignment: " + "'%s' object layout differs from '%s'", + new->tp_name, + old->tp_name); + return -1; + } + if (new->tp_flags & Py_TPFLAGS_HEAPTYPE) { + Py_INCREF(new); + } + self->ob_type = new; + if (old->tp_flags & Py_TPFLAGS_HEAPTYPE) { + Py_DECREF(old); + } + return 0; +} + +static PyGetSetDef object_getsets[] = { + {"__class__", object_get_class, object_set_class, + "the object's class"}, {0} }; @@ -1227,8 +1306,8 @@ PyTypeObject PyBaseObject_Type = { 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ - object_members, /* tp_members */ - 0, /* tp_getset */ + 0, /* tp_members */ + object_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ -- cgit v0.12