summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_descr.py28
-rw-r--r--Objects/typeobject.c87
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 */