Patch #1623563: allow __class__ assignment for classes with __slots__.
The old and the new class are still required to have the same slot
names, but the order in which they are specified is not relevant.
diff --git a/Doc/ref/ref3.tex b/Doc/ref/ref3.tex
index c5dbfd2..c0f3219 100644
--- a/Doc/ref/ref3.tex
+++ b/Doc/ref/ref3.tex
@@ -1593,6 +1593,11 @@
Mappings may also be used; however, in the future, special meaning may
be assigned to the values corresponding to each key.
+\item \var{__class__} assignment works only if both classes have the
+same \var{__slots__}.
+\versionchanged[Previously, \var{__class__} assignment raised an error
+if either new or old class had \var{__slots__}]{2.6}
+
\end{itemize}
@@ -2223,3 +2228,6 @@
Python \keyword{with} statement.}
\end{seealso}
+
+
+
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 71f4ec4..5abecf4 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -2853,6 +2853,51 @@
cant(o, type(1))
cant(o, type(None))
del o
+ class G(object):
+ __slots__ = ["a", "b"]
+ class H(object):
+ __slots__ = ["b", "a"]
+ try:
+ unicode
+ except NameError:
+ class I(object):
+ __slots__ = ["a", "b"]
+ else:
+ class I(object):
+ __slots__ = [unicode("a"), unicode("b")]
+ class J(object):
+ __slots__ = ["c", "b"]
+ class K(object):
+ __slots__ = ["a", "b", "d"]
+ class L(H):
+ __slots__ = ["e"]
+ class M(I):
+ __slots__ = ["e"]
+ class N(J):
+ __slots__ = ["__weakref__"]
+ class P(J):
+ __slots__ = ["__dict__"]
+ class Q(J):
+ pass
+ class R(J):
+ __slots__ = ["__dict__", "__weakref__"]
+
+ for cls, cls2 in ((G, H), (G, I), (I, H), (Q, R), (R, Q)):
+ x = cls()
+ x.a = 1
+ x.__class__ = cls2
+ verify(x.__class__ is cls2,
+ "assigning %r as __class__ for %r silently failed" % (cls2, x))
+ vereq(x.a, 1)
+ x.__class__ = cls
+ verify(x.__class__ is cls,
+ "assigning %r as __class__ for %r silently failed" % (cls, x))
+ vereq(x.a, 1)
+ for cls in G, J, K, L, M, N, P, R, list, Int:
+ for cls2 in G, J, K, L, M, N, P, R, list, Int:
+ if cls is cls2:
+ continue
+ cant(cls(), cls2)
def setdict():
if verbose: print "Testing __dict__ assignment..."
diff --git a/Misc/ACKS b/Misc/ACKS
index 7b6002c..eeb266a 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -276,6 +276,7 @@
Ivan Herman
Jürgen Hermann
Gary Herron
+Thomas Herve
Bernhard Herzog
Magnus L. Hetland
Raymond Hettinger
diff --git a/Misc/NEWS b/Misc/NEWS
index 8af1eb1..6e336b6 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@
Core and builtins
-----------------
+- Patch #1623563: allow __class__ assignment for classes with __slots__.
+ The old and the new class are still required to have the same slot names.
+
- Patch #1642547: Fix an error/crash when encountering syntax errors in
complex if statements.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 64abe5a..64003fc 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -1844,8 +1844,11 @@
}
}
- /* Copy slots into yet another tuple, demangling names */
- newslots = PyTuple_New(nslots - add_dict - add_weak);
+ /* Copy slots into a list, mangle names and sort them.
+ Sorted names are needed for __class__ assignment.
+ Convert them back to tuple at the end.
+ */
+ newslots = PyList_New(nslots - add_dict - add_weak);
if (newslots == NULL)
goto bad_slots;
for (i = j = 0; i < nslots; i++) {
@@ -1858,13 +1861,23 @@
tmp =_Py_Mangle(name, tmp);
if (!tmp)
goto bad_slots;
- PyTuple_SET_ITEM(newslots, j, tmp);
+ PyList_SET_ITEM(newslots, j, tmp);
j++;
}
assert(j == nslots - add_dict - add_weak);
nslots = j;
Py_DECREF(slots);
- slots = newslots;
+ if (PyList_Sort(newslots) == -1) {
+ Py_DECREF(bases);
+ Py_DECREF(newslots);
+ return NULL;
+ }
+ slots = PyList_AsTuple(newslots);
+ Py_DECREF(newslots);
+ if (slots == NULL) {
+ Py_DECREF(bases);
+ return NULL;
+ }
/* Secondary bases may provide weakrefs or dict */
if (nbases > 1 &&
@@ -2481,6 +2494,7 @@
{
PyTypeObject *base = a->tp_base;
Py_ssize_t size;
+ PyObject *slots_a, *slots_b;
if (base != b->tp_base)
return 0;
@@ -2491,6 +2505,15 @@
size += sizeof(PyObject *);
if (a->tp_weaklistoffset == size && b->tp_weaklistoffset == size)
size += sizeof(PyObject *);
+
+ /* Check slots compliance */
+ slots_a = ((PyHeapTypeObject *)a)->ht_slots;
+ slots_b = ((PyHeapTypeObject *)b)->ht_slots;
+ if (slots_a && slots_b) {
+ if (PyObject_Compare(slots_a, slots_b) != 0)
+ return 0;
+ size += sizeof(PyObject *) * PyTuple_GET_SIZE(slots_a);
+ }
return size == a->tp_basicsize && size == b->tp_basicsize;
}