# The most derived metaclass of D is A rather than type.
class D(B, C):
pass
- new_calls[:] = []
+ self.assertIs(A, type(D))
+
+ # issue1294232: correct metaclass calculation
+ new_calls = [] # to check the order of __new__ calls
+ class AMeta(type):
+ @staticmethod
+ def __new__(mcls, name, bases, ns):
+ new_calls.append('AMeta')
+ return super().__new__(mcls, name, bases, ns)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ return {}
+
+ class BMeta(AMeta):
+ @staticmethod
+ def __new__(mcls, name, bases, ns):
+ new_calls.append('BMeta')
+ return super().__new__(mcls, name, bases, ns)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ ns = super().__prepare__(name, bases)
+ ns['BMeta_was_here'] = True
+ return ns
+
+ class A(metaclass=AMeta):
+ pass
+ self.assertEqual(['AMeta'], new_calls)
- new_calls[:] = []
++ new_calls.clear()
+
+ class B(metaclass=BMeta):
+ pass
+ # BMeta.__new__ calls AMeta.__new__ with super:
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
- new_calls[:] = []
++ new_calls.clear()
+
+ class C(A, B):
+ pass
+ # The most derived metaclass is BMeta:
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
- new_calls[:] = []
++ new_calls.clear()
+ # BMeta.__prepare__ should've been called:
+ self.assertIn('BMeta_was_here', C.__dict__)
+
+ # The order of the bases shouldn't matter:
+ class C2(B, A):
+ pass
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
- new_calls[:] = []
++ new_calls.clear()
+ self.assertIn('BMeta_was_here', C2.__dict__)
+
+ # Check correct metaclass calculation when a metaclass is declared:
+ class D(C, metaclass=type):
+ pass
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
- new_calls[:] = []
++ new_calls.clear()
+ self.assertIn('BMeta_was_here', D.__dict__)
+
+ class E(C, metaclass=AMeta):
+ pass
+ self.assertEqual(['BMeta', 'AMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+ self.assertIn('BMeta_was_here', E.__dict__)
+
+ # Special case: the given metaclass isn't a class,
+ # so there is no metaclass calculation.
+ marker = object()
+ def func(*args, **kwargs):
+ return marker
+ class X(metaclass=func):
+ pass
+ class Y(object, metaclass=func):
+ pass
+ class Z(D, metaclass=func):
+ pass
+ self.assertIs(marker, X)
+ self.assertIs(marker, Y)
+ self.assertIs(marker, Z)
+
+ # The given metaclass is a class,
+ # but not a descendant of type.
+ prepare_calls = [] # to track __prepare__ calls
+ class ANotMeta:
+ def __new__(mcls, *args, **kwargs):
+ new_calls.append('ANotMeta')
+ return super().__new__(mcls)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ prepare_calls.append('ANotMeta')
+ return {}
+ class BNotMeta(ANotMeta):
+ def __new__(mcls, *args, **kwargs):
+ new_calls.append('BNotMeta')
+ return super().__new__(mcls)
+ @classmethod
+ def __prepare__(mcls, name, bases):
+ prepare_calls.append('BNotMeta')
+ return super().__prepare__(name, bases)
+
+ class A(metaclass=ANotMeta):
+ pass
+ self.assertIs(ANotMeta, type(A))
+ self.assertEqual(['ANotMeta'], prepare_calls)
- new_calls[:] = []
++ prepare_calls.clear()
+ self.assertEqual(['ANotMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+
+ class B(metaclass=BNotMeta):
+ pass
+ self.assertIs(BNotMeta, type(B))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
- new_calls[:] = []
++ prepare_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
- new_calls[:] = []
++ new_calls.clear()
+
+ class C(A, B):
+ pass
+ self.assertIs(BNotMeta, type(C))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
- new_calls[:] = []
++ prepare_calls.clear()
+
+ class C2(B, A):
+ pass
+ self.assertIs(BNotMeta, type(C2))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
- new_calls[:] = []
++ prepare_calls.clear()
+
+ # This is a TypeError, because of a metaclass conflict:
+ # BNotMeta is neither a subclass, nor a superclass of type
+ with self.assertRaises(TypeError):
+ class D(C, metaclass=type):
+ pass
+
+ class E(C, metaclass=ANotMeta):
+ pass
+ self.assertIs(BNotMeta, type(E))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
- new_calls[:] = []
++ prepare_calls.clear()
+
+ class F(object(), C):
+ pass
+ self.assertIs(BNotMeta, type(F))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
- new_calls[:] = []
++ prepare_calls.clear()
+
+ class F2(C, object()):
+ pass
+ self.assertIs(BNotMeta, type(F2))
+ self.assertEqual(['BNotMeta', 'ANotMeta'], new_calls)
- prepare_calls[:] = []
++ new_calls.clear()
+ self.assertEqual(['BNotMeta', 'ANotMeta'], prepare_calls)
++ prepare_calls.clear()
+
+ # TypeError: BNotMeta is neither a
+ # subclass, nor a superclass of int
+ with self.assertRaises(TypeError):
+ class X(C, int()):
+ pass
+ with self.assertRaises(TypeError):
+ class X(int(), C):
+ pass
def test_module_subclasses(self):
# Testing Python subclass of module...
static PyObject *
builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
{
- PyObject *func, *name, *bases, *mkw, *meta, *prep, *ns, *cell;
+ PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *cell;
PyObject *cls = NULL;
- Py_ssize_t nargs;
+ Py_ssize_t nargs, nbases;
+ int isclass;
+ _Py_IDENTIFIER(__prepare__);
assert(args != NULL);
if (!PyTuple_Check(args)) {
meta = (PyObject *) (base0->ob_type);
}
Py_INCREF(meta);
+ isclass = 1; /* meta is really a class */
+ }
++
+ if (isclass) {
+ /* meta is really a class, so check for a more derived
+ metaclass, or possible metaclass conflicts: */
+ winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta,
+ bases);
+ if (winner == NULL) {
+ Py_DECREF(meta);
+ Py_XDECREF(mkw);
+ Py_DECREF(bases);
+ return NULL;
+ }
+ if (winner != meta) {
+ Py_DECREF(meta);
+ meta = winner;
+ Py_INCREF(meta);
+ }
}
- prep = PyObject_GetAttrString(meta, "__prepare__");
+ /* else: meta is not a class, so we cannot do the metaclass
+ calculation, so we will use the explicitly given object as it is */
+ prep = _PyObject_GetAttrId(meta, &PyId___prepare__);
if (prep == NULL) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();