mirror of https://github.com/python/cpython
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:
parent
2306d246e8
commit
5c294fb0e6
|
@ -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__":
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue