]> granicus.if.org Git - python/commitdiff
Make __class__ assignment possible, when the object structures are the
authorGuido van Rossum <guido@python.org>
Tue, 25 Sep 2001 03:43:42 +0000 (03:43 +0000)
committerGuido van Rossum <guido@python.org>
Tue, 25 Sep 2001 03:43:42 +0000 (03:43 +0000)
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
Objects/typeobject.c

index 5bd837e6f27bbad5c2bbb2d924b50948c099d017..766c399dcd90cf851af49d4db8552eb939248bd2 100644 (file)
@@ -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__":
index 027a568dec971f3810659fc3ad474f82b68155ee..877a3bd870587ac555c76543e388cee7337b2738 100644 (file)
@@ -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 */