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)
This commit is contained in:
Guido van Rossum 2001-09-25 03:43:42 +00:00
parent 2306d246e8
commit 5c294fb0e6
2 changed files with 111 additions and 4 deletions

View 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__":

View 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 */