diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1577,10 +1577,21 @@ executed in a new namespace and the class name is bound locally to the result of ``type(name, bases, namespace)``. -The class creation process can be customised by passing the ``metaclass`` -keyword argument in the class definition line, or by inheriting from an -existing class that included such an argument. In the following example, -both ``MyClass`` and ``MySubclass`` are instances of ``Meta``:: +The class creation process can be customised by defining a class +initialization hook, or by using a metaclass (or both). A class +initialization hook can be defined by creating a class method +called ``__init_class__`` (or inheriting one), as in the following +example:: + + class MyClass: + @classmethod + def __init_class__(cls, ns): + ... # initialize cls + +A metaclass can be used by passing the ``metaclass`` keyword argument +in the class definition line, or by inheriting from an existing class +that included such an argument. In the following example, both +``MyClass`` and ``MySubclass`` are instances of ``Meta``:: class Meta(type): pass @@ -1600,6 +1611,7 @@ * the class namespace is prepared * the class body is executed * the class object is created +* the class initialization hook is called (if it exists) Determining the appropriate metaclass ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1630,11 +1642,23 @@ If the metaclass has no ``__prepare__`` attribute, then the class namespace is initialised as an empty :func:`dict` instance. +The __prepare__ method of the default metaclass (:func:`type`) returns a new +empty :func:`dict` instance by default. But if its ``namespace`` optional +keyword-only argument is used, it returns the object passed to that argument. +This mechanism allows passing a namespace in the class header (``MyClass.a`` +will be ``1`` in the following example):: + + class MyClass(namespace={'a': 1}): + pass + .. seealso:: :pep:`3115` - Metaclasses in Python 3000 Introduced the ``__prepare__`` namespace hook + :pep:`422` - Simpler customisation of class creation + Introduced the ``namespace`` optional keyword-only argument. + Executing the class body ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1667,16 +1691,37 @@ lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method. -After the class object is created, it is passed to the class decorators -included in the class definition (if any) and the resulting object is bound -in the local namespace as the defined class. - .. seealso:: :pep:`3135` - New super Describes the implicit ``__class__`` closure reference +Calling the class initialization hook +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After the class object is created, and the ``__class__`` reference is +initialized, the ``__init_class__`` method of the class object is called +as ``cls.__init_class__(ns)``, where ``ns`` is a read-only view of the +mapping originally returned by ``__prepare__``. If there is no +``__init_class__`` method, this step is omitted. The ``__init_class__`` +method can use the usual :class:`super` mechanisms, so multiple inheritance +is supported. :class:`object` has an ``__init_class__`` method (which does +nothing), so it is safe to call ``super().__init_class__(ns)`` without +checking if it exists. Note, that when the class initialization hook is +called, the class object is not bound to the class name yet, so +``__init_class__`` cannot refer to the class object with its name. + +After the initialization hook is called, the class object is passed to the +class decorators included in the class definition (if any) and the resulting +object is bound in the local namespace as the defined class. + +.. seealso:: + + :pep:`422` - Simpler customisation of class creation + Introduced the ``__init_class__`` hook. + + Metaclass example ^^^^^^^^^^^^^^^^^ diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -5,6 +5,7 @@ import math import unittest import weakref +import collections from copy import deepcopy from test import support @@ -796,6 +797,235 @@ class X(int(), C): pass + def test___init_class__(self): + # PEP 422: Simple class initialisation hook + class C: + @classmethod + def __init_class__(cls, ns): + cls.x = 0 + self.assertEqual(C.x, 0) + # inherited: + class D(C): + pass + self.assertEqual(D.__dict__['x'], 0) + # overwrite: + class E(C): + x = 1 + self.assertEqual(E.__dict__['x'], 0) + # override: + class F(C): + @classmethod + def __init_class__(cls, ns): + cls.y = 1 + self.assertEqual(F.y, 1) + self.assertEqual(F.x, 0) + self.assertNotIn('x', F.__dict__) + self.assertFalse(hasattr(C, 'y')) + # super: + class G(C): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.y = 1 + self.assertEqual(G.y, 1) + self.assertEqual(G.__dict__['x'], 0) + self.assertFalse(hasattr(C, 'y')) + # staticmethod: + class H(C): + @staticmethod + def __init_class__(ns): + __class__.z = 2 + self.assertEqual(H.z, 2) + self.assertFalse(hasattr(C, 'z')) + # __class__: + class I: + @classmethod + def __init_class__(cls, ns): + cls.x = 0 + __class__.y += 1 + y = 0 + self.assertEqual(I.x, 0) + self.assertEqual(I.y, 1) + class J(I): + pass + self.assertEqual(J.__dict__['x'], 0) + self.assertEqual(I.y, 2) + class K(J): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + __class__.z += 1 + z = 0 + self.assertEqual(K.__dict__['x'], 0) + self.assertEqual(I.y, 3) + self.assertEqual(K.z, 1) + self.assertFalse(hasattr(J, 'z')) + # multiple inheritance: + class L: + @classmethod + def __init_class__(cls, ns): + pass + class M(L): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.x = 0 + self.assertEqual(M.x, 0) + class N(L): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.y = 1 + self.assertEqual(N.y, 1) + class O(M, N): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.z = 2 + self.assertEqual(O.__dict__['x'], 0) + self.assertEqual(O.__dict__['y'], 1) + self.assertEqual(O.__dict__['z'], 2) + # object.__init_class__: + self.assertIsNone(object.__init_class__({})) + self.assertIsNone(type.__init_class__({})) # inherited from object + self.assertIsNone(int.__init_class__({})) # inherited from object + class X: + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) # this shouldn't raise + # decorators: + def dec1(cls): + cls.x = 1 + return cls + def dec2(cls): + cls.x = 2 + return cls + @dec2 + @dec1 + class P: + @classmethod + def __init_class__(cls, ns): + cls.x = 0 + self.assertEqual(P.x, 2) + # block class initialization: + class Meta(type): + def __getattribute__(cls, name): + if name == '__init_class__': + raise AttributeError('__init_class__') + return super().__getattribute__(name) + class Q(C, metaclass=Meta): + pass + self.assertNotIn('x', Q.__dict__) + # other exceptions should be propagated: + class Meta2(type): + def __getattribute__(cls, name): + if name == '__init_class__': + raise KeyError('xxx') + return super().__getattribute__(name) + R = sentinel = object() + with self.assertRaisesRegex(KeyError, 'xxx'): + class R(C, metaclass=Meta2): + pass + self.assertIs(R, sentinel) + # __init_class__ raises an exception: + S = sentinel + with self.assertRaisesRegex(KeyError, 'xxx'): + class S: + @classmethod + def __init_class__(cls, ns): + raise KeyError('xxx') + self.assertIs(S, sentinel) + # namespace argument: + class T: + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.namespace = ns + self.assertIsInstance(T.namespace, types.MappingProxyType) + self.assertIs(T.namespace['__init_class__'], T.__dict__['__init_class__']) + class U(T, namespace=collections.OrderedDict([('a', 1), ('b', 2)])): + c = 3 + d = 4 + self.assertEqual(list(filter(lambda x: x[0] in {'a', 'b', 'c', 'd'}, U.namespace.items())), + [('a', 1), ('b', 2), ('c', 3), ('d', 4)]) + # mutating the namespace in __init_class__ is forbidden: + with self.assertRaisesRegex(TypeError, "'mappingproxy' object does not support item assignment"): + class V(namespace={'a': 1}): + @classmethod + def __init_class__(cls, ns): + ns['b'] = 2 + # metaclasses with __init_class__: + class MetaA(type): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.a = 1 + self.assertEqual(MetaA.__dict__['a'], 1) + class MetaB(type): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.b = 2 + self.assertEqual(MetaB.__dict__['b'], 2) + class MetaC(MetaA, MetaB): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.c = 3 + self.assertEqual(MetaC.__dict__['a'], 1) + self.assertEqual(MetaC.__dict__['b'], 2) + self.assertEqual(MetaC.__dict__['c'], 3) + class RegularClass(metaclass=MetaC): + @classmethod + def __init_class__(cls, ns): + cls.x = 0 + self.assertEqual(RegularClass.__dict__['x'], 0) + self.assertEqual(RegularClass.a, 1) + self.assertEqual(RegularClass.b, 2) + self.assertEqual(RegularClass.c, 3) + self.assertEqual(RegularClass().x, 0) + self.assertFalse(hasattr(RegularClass(), 'a')) + self.assertFalse(hasattr(RegularClass(), 'b')) + self.assertFalse(hasattr(RegularClass(), 'c')) + + # DynamicDecorators example from the PEP: + def set_x(cls): + cls.x = 0 + return cls + def set_x_1(cls): + cls.x = 1 + return cls + def set_y(cls): + cls.y = 1 + return cls + def set_z(cls): + cls.z = 2 + return cls + + class DynamicDecoratorsBase: + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + cls.x = 42 + + class DynamicDecorators(DynamicDecoratorsBase): + @classmethod + def __init_class__(cls, ns): + super().__init_class__(ns) + for entry in reversed(cls.mro()): + decorators = entry.__dict__.get("__decorators__", ()) + for deco in reversed(decorators): + cls = deco(cls) + __decorators__ = [set_x, set_y, set_x_1] + self.assertEqual(DynamicDecorators.x, 0) + self.assertEqual(DynamicDecorators.y, 1) + + class DynamicDecoratorsSub(DynamicDecorators): + __decorators__ = [set_z, set_x_1] + self.assertEqual(DynamicDecoratorsSub.x, 1) + self.assertEqual(DynamicDecoratorsSub.y, 1) + self.assertEqual(DynamicDecoratorsSub.z, 2) + def test_module_subclasses(self): # Testing Python subclass of module... log = [] diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py --- a/Lib/test/test_descrtut.py +++ b/Lib/test/test_descrtut.py @@ -182,6 +182,7 @@ '__iadd__', '__imul__', '__init__', + '__init_class__', '__iter__', '__le__', '__len__', diff --git a/Lib/test/test_metaclass.py b/Lib/test/test_metaclass.py --- a/Lib/test/test_metaclass.py +++ b/Lib/test/test_metaclass.py @@ -215,6 +215,57 @@ {} >>> +It must accept a 'namespace' keyword-only argument, and return it. + + >>> mydict = {} + >>> d = type.__prepare__(namespace=mydict) + >>> d is mydict + True + >>> class MyClass(namespace={'a': 1, 'b': 2}): + ... print(a) + ... print(b) + ... + 1 + 2 + >>> MyClass.a + 1 + >>> MyClass.b + 2 + >>> + +Its __new__ and __init__ should also accept (and ignore) it. + + >>> MyClass2 = type.__new__(type, "MyClass2", (), dict(a=1), namespace=dict(b=2)) + >>> MyClass2.a + 1 + >>> MyClass2.b + Traceback (most recent call last): + [...] + AttributeError: type object 'MyClass2' has no attribute 'b' + >>> MyClass2.__init__("MyClass2", (), dict(a=1), namespace=dict(b=2)) + >>> MyClass2.a + 1 + >>> MyClass2.b + Traceback (most recent call last): + [...] + AttributeError: type object 'MyClass2' has no attribute 'b' + >>> MyClass3 = type("MyClass3", (), dict(a=1), namespace=dict(b=2)) + >>> MyClass3.a + 1 + >>> MyClass3.b + Traceback (most recent call last): + [...] + AttributeError: type object 'MyClass3' has no attribute 'b' + >>> type("MyClass4", (), {}, namespace={}, foo=None) + Traceback (most recent call last): + [...] + TypeError: type() takes 1, 3 or 4 arguments + >>> type("MyClass5", (), {}, foo=None) + Traceback (most recent call last): + [...] + TypeError: 'foo' is an invalid keyword argument for this function + >>> + Make sure it works with subclassing. >>> class M(type): @@ -230,6 +281,15 @@ 42 >>> print(C.hello) 42 + >>> class D(metaclass=M, namespace={'a': 1}): + ... print(hello) + ... print(a) + 42 + 1 + >>> print(D.hello) + 42 + >>> print(D.a) + 1 >>> Test failures in looking up the __prepare__ method work. diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -438,8 +438,9 @@ del expected['__doc__'] del expected['__class__'] # inspect resolves descriptors on type into methods, but vars doesn't, - # so we need to update __subclasshook__. + # so we need to update __subclasshook__ and __init_class__. expected['__subclasshook__'] = TestClass.__subclasshook__ + expected['__init_class__'] = TestClass.__init_class__ methods = pydoc.allmethods(TestClass) self.assertDictEqual(methods, expected) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -996,6 +996,121 @@ with self.assertRaises(TypeError): X = types.new_class("X", (int(), C)) + def test___init_class__(self): + # PEP 422: Simple class initialisation hook + def c(ns): + @classmethod + def __init_class__(cls, ns): + cls.x = 0 + ns['__init_class__'] = __init_class__ + C = types.new_class("C", exec_body=c) + self.assertEqual(C.x, 0) + # inherited: + D = types.new_class("D", (C,)) + self.assertEqual(D.__dict__['x'], 0) + # overwrite: + def e(ns): + ns['x'] = 1 + E = types.new_class("E", (C,), exec_body=e) + self.assertEqual(E.__dict__['x'], 0) + # override: + def f(ns): + @classmethod + def __init_class__(cls, ns): + cls.y = 1 + ns['__init_class__'] = __init_class__ + F = types.new_class("F", (C,), exec_body=f) + self.assertEqual(F.y, 1) + self.assertEqual(F.x, 0) + self.assertNotIn('x', F.__dict__) + self.assertFalse(hasattr(C, 'y')) + # staticmethod: + z = 0 + def h(ns): + @staticmethod + def __init_class__(ns): + nonlocal z + z = 2 + ns['__init_class__'] = __init_class__ + H = types.new_class("H", (C,), exec_body=h) + self.assertEqual(z, 2) + # block class initialization: + class Meta(type): + def __getattribute__(cls, name): + if name == '__init_class__': + raise AttributeError('__init_class__') + return super().__getattribute__(name) + Q = types.new_class("Q", (C,), dict(metaclass=Meta)) + self.assertNotIn('x', Q.__dict__) + # other exceptions should be propagated: + class Meta2(type): + def __getattribute__(cls, name): + if name == '__init_class__': + raise KeyError('xxx') + return super().__getattribute__(name) + R = sentinel = object() + with self.assertRaisesRegex(KeyError, 'xxx'): + R = types.new_class("R", (C,), dict(metaclass=Meta2)) + self.assertIs(R, sentinel) + # __init_class__ raises an exception: + def s(ns): + @classmethod + def __init_class__(cls, ns): + raise KeyError('xxx') + ns['__init_class__'] = __init_class__ + S = sentinel + with self.assertRaisesRegex(KeyError, 'xxx'): + S = types.new_class("S", exec_body=s) + self.assertIs(S, sentinel) + # namespace argument: + def t(ns): + @classmethod + def __init_class__(cls, nspace): + cls.namespace = nspace + ns['__init_class__'] = __init_class__ + T = types.new_class("T", exec_body=t) + self.assertIsInstance(T.namespace, types.MappingProxyType) + self.assertIs(T.namespace['__init_class__'], T.__dict__['__init_class__']) + def u(ns): + ns['c'] = 3 + ns['d'] = 4 + @classmethod + def __init_class__(cls, nspace): + cls.namespace = nspace + ns['__init_class__'] = __init_class__ + U = types.new_class("U", (), + dict(namespace=collections.OrderedDict([('a', 1), ('b', 2)])), + exec_body=u) + self.assertEqual(list(filter(lambda x: x[0] in {'a', 'b', 'c', 'd'}, U.namespace.items())), + [('a', 1), ('b', 2), ('c', 3), ('d', 4)]) + # mutating the namespace in __init_class__ is forbidden: + def v(ns): + @classmethod + def __init_class__(cls, nspace): + nspace['b'] = 2 + ns['__init_class__'] = __init_class__ + V = sentinel + with self.assertRaisesRegex(TypeError, "'mappingproxy' object does not support item assignment"): + V = types.new_class("V", (), dict(namespace={'a': 1}), exec_body=v) + # metaclass with __init_class__: + def meta(ns): + @classmethod + def __init_class__(cls, ns): + cls.x = 0 + ns['__init_class__'] = __init_class__ + Meta = types.new_class("Meta", (type,), exec_body=meta) + self.assertEqual(Meta.__dict__['x'], 0) + def x(ns): + @classmethod + def __init_class__(cls, ns): + cls.y = 1 + ns['__init_class__'] = __init_class__ + X = types.new_class("X", (), dict(metaclass=Meta), exec_body=x) + self.assertEqual(X.__dict__['y'], 1) + self.assertEqual(X.x, 0) + self.assertEqual(X().y, 1) + self.assertFalse(hasattr(X(), 'x')) + class SimpleNamespaceTests(unittest.TestCase): diff --git a/Lib/types.py b/Lib/types.py --- a/Lib/types.py +++ b/Lib/types.py @@ -49,7 +49,14 @@ meta, ns, kwds = prepare_class(name, bases, kwds) if exec_body is not None: exec_body(ns) - return meta(name, bases, ns, **kwds) + cls = meta(name, bases, ns, **kwds) + try: + initcl = cls.__init_class__ + except AttributeError: + pass + else: + initcl(MappingProxyType(ns)) + return cls def prepare_class(name, bases=(), kwds=None): """Call the __prepare__ method of the appropriate metaclass. diff --git a/Objects/typeobject.c b/Objects/typeobject.c --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1915,10 +1915,23 @@ assert(args != NULL && PyTuple_Check(args)); assert(kwds == NULL || PyDict_Check(kwds)); - if (kwds != NULL && PyDict_Check(kwds) && PyDict_Size(kwds) != 0) { - PyErr_SetString(PyExc_TypeError, - "type.__init__() takes no keyword arguments"); - return -1; + if ((kwds != NULL) && PyDict_Check(kwds)) { + Py_ssize_t nkwds = PyDict_Size(kwds); + if (nkwds == 1) { + /* the only keyword argument must be 'namespace': */ + if (PyDict_GetItemString(kwds, "namespace") == NULL) { + PyErr_SetString(PyExc_TypeError, + "type.__init__() may only take a 'namespace' " + "keyword argument"); + return -1; + } + /* else: OK, namespace is passed, we ignore it */ + } + else if (nkwds != 0) { + PyErr_SetString(PyExc_TypeError, + "type.__init__() takes 0 or 1 keyword arguments"); + return -1; + } } if (args != NULL && PyTuple_Check(args) && @@ -1982,7 +1995,8 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) { PyObject *name, *bases = NULL, *orig_dict, *dict = NULL; - static char *kwlist[] = {"name", "bases", "dict", 0}; + PyObject *namespace = NULL; + static char *kwlist[] = {"name", "bases", "dict", "namespace", 0}; PyObject *qualname, *slots = NULL, *tmp, *newslots; PyTypeObject *type = NULL, *base, *tmptype, *winner; PyHeapTypeObject *et; @@ -1999,6 +2013,7 @@ { const Py_ssize_t nargs = PyTuple_GET_SIZE(args); const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_Size(kwds); + const Py_ssize_t argcount = nargs + nkwds; if (PyType_CheckExact(metatype) && nargs == 1 && nkwds == 0) { PyObject *x = PyTuple_GET_ITEM(args, 0); @@ -2006,21 +2021,23 @@ return (PyObject *) Py_TYPE(x); } - /* SF bug 475327 -- if that didn't trigger, we need 3 + /* SF bug 475327 -- if that didn't trigger, we need 3 or 4 arguments. but PyArg_ParseTupleAndKeywords below may give a msg saying type() needs exactly 3. */ - if (nargs + nkwds != 3) { + if ((argcount != 3) && (argcount != 4)) { PyErr_SetString(PyExc_TypeError, - "type() takes 1 or 3 arguments"); + "type() takes 1, 3 or 4 arguments"); return NULL; } } - /* Check arguments: (name, bases, dict) */ - if (!PyArg_ParseTupleAndKeywords(args, kwds, "UO!O!:type", kwlist, + /* Check arguments: (name, bases, dict, *, namespace=NULL) + (namespace is ignored, it is only used by __prepare__) */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "UO!O!|$O:type", kwlist, &name, &PyTuple_Type, &bases, - &PyDict_Type, &orig_dict)) + &PyDict_Type, &orig_dict, + &namespace)) return NULL; /* Determine the proper metatype to deal with this: */ @@ -2747,6 +2764,14 @@ static PyObject * type_prepare(PyObject *self, PyObject *args, PyObject *kwds) { + if (kwds != NULL) { + PyObject *ns = PyDict_GetItemString(kwds, "namespace"); + if (ns != NULL) { + Py_INCREF(ns); + return ns; + } + } + return PyDict_New(); } @@ -3058,6 +3083,12 @@ } static PyObject * +object_init_class(PyTypeObject *cls, PyObject *ns) +{ + Py_RETURN_NONE; +} + +static PyObject * object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { if (excess_args(args, kwds) && @@ -3745,6 +3776,8 @@ PyDoc_STR("__sizeof__() -> int\nsize of object in memory, in bytes")}, {"__dir__", object_dir, METH_NOARGS, PyDoc_STR("__dir__() -> list\ndefault dir() implementation")}, + {"__init_class__", (PyCFunction)object_init_class, METH_O | METH_CLASS, + PyDoc_STR("__init_class__(ns) -> None\ninitializes a class object")}, {0} }; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -38,11 +38,13 @@ static PyObject * builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds) { - PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *cell; + PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns; + PyObject *cell, *initcl, *res; PyObject *cls = NULL; Py_ssize_t nargs; int isclass; _Py_IDENTIFIER(__prepare__); + _Py_IDENTIFIER(__init_class__); assert(args != NULL); if (!PyTuple_Check(args)) { @@ -163,8 +165,54 @@ cls = PyEval_CallObjectWithKeywords(meta, margs, mkw); Py_DECREF(margs); } - if (cls != NULL && PyCell_Check(cell)) - PyCell_Set(cell, cls); + if (cls != NULL) { + /* initialize the __class__ reference: */ + if (PyCell_Check(cell)) { + PyCell_Set(cell, cls); + } + /* call __init_class__: */ + initcl = _PyObject_GetAttrId(cls, &PyId___init_class__); + if (initcl == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + /* no __init_class__, nothing to do */ + } + else { + /* propagate other exceptions: */ + Py_DECREF(cls); + cls = NULL; + } + } + else { + /* create a read-only view of the ns: */ + PyObject *proxy = PyDictProxy_New(ns); + if (proxy == NULL) { + Py_DECREF(cls); + cls = NULL; + } + else { + /* put it in a tuple: */ + PyObject *initcl_args = PyTuple_Pack(1, proxy); + if (initcl_args == NULL) { + Py_DECREF(cls); + cls = NULL; + } + else { + /* and pass it to __init_class__: */ + res = PyObject_CallObject(initcl, initcl_args); + Py_DECREF(initcl_args); + if (res == NULL) { + /* __init_class__ raised an exception */ + Py_DECREF(cls); + cls = NULL; + } + Py_XDECREF(res); + } + Py_DECREF(proxy); + } + Py_DECREF(initcl); + } + } Py_DECREF(cell); } Py_DECREF(ns);