From c0c8f92b8f471be3aa2ee49cca32fab67ba2e2a2 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sun, 27 Oct 2013 15:53:43 +0100 Subject: [PATCH] implement metaclass calculation/validation algorithm, make classes inherit their parents' metaclass --- CHANGES.rst | 13 +++ Cython/Compiler/ExprNodes.py | 36 ++++++-- Cython/Compiler/Nodes.py | 86 +++++++++++------- Cython/Compiler/Parsing.py | 13 +-- Cython/Utility/ObjectHandling.c | 196 +++++++++++++++++++++++++++++++--------- tests/run/locals_T732.pyx | 2 +- tests/run/metaclass.pyx | 29 +++++- 7 files changed, 284 insertions(+), 91 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 23bc7ba..9585f39 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -52,6 +52,16 @@ Features added Bugs fixed ---------- +* The metaclass of a Python class was not inherited from its parent + class(es). It is now extracted from the list of base classes if not + provided explicitly using the Py3 ``metaclass`` keyword argument. + In Py2 compilation mode, a ``__metaclass__`` entry in the class + dict will still take precedence if not using Py3 metaclass syntax, + but only *after* creating the class dict (which may have been done + by a metaclass of a base class, see PEP 3115). It is generally + recommended to use the explicit Py3 syntax to define metaclasses + for Python types at compile time. + * The automatic C switch statement generation behaves more safely for heterogeneous value types (e.g. mixing enum and char), allowing for a slightly wider application and reducing corner cases. It now always @@ -61,6 +71,9 @@ Bugs fixed Other changes ------------- +* In Py3 compilation mode, Python2-style metaclasses declared by a + ``__metaclass__`` class dict entry are ignored. + * In Py3.4+, the Cython generator type uses ``tp_finalize()`` for safer cleanup instead of ``tp_del()``. diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 3d91371..4551e1a 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -6782,6 +6782,8 @@ class Py3ClassNode(ExprNode): # name EncodedString Name of the class # dict ExprNode Class dict (not owned by this node) # module_name EncodedString Name of defining module + # calculate_metaclass bool should call CalculateMetaclass() + # allow_py2_metaclass bool should look for Py2 metaclass subexprs = [] @@ -6798,14 +6800,20 @@ class Py3ClassNode(ExprNode): def generate_result_code(self, code): code.globalstate.use_utility_code(UtilityCode.load_cached("Py3ClassCreate", "ObjectHandling.c")) cname = code.intern_identifier(self.name) + if self.mkw: + mkw = self.mkw.py_result() + else: + mkw = 'NULL' code.putln( - '%s = __Pyx_Py3ClassCreate(%s, %s, %s, %s, %s); %s' % ( + '%s = __Pyx_Py3ClassCreate(%s, %s, %s, %s, %s, %d, %d); %s' % ( self.result(), self.metaclass.result(), cname, self.bases.py_result(), self.dict.py_result(), - self.mkw.py_result(), + mkw, + self.calculate_metaclass, + self.allow_py2_metaclass, code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -6941,12 +6949,20 @@ class PyClassMetaclassNode(ExprNode): return True def generate_result_code(self, code): - code.globalstate.use_utility_code(UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c")) - code.putln( - "%s = __Pyx_Py3MetaclassGet(%s, %s); %s" % ( - self.result(), + if self.mkw: + code.globalstate.use_utility_code( + UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c")) + call = "__Pyx_Py3MetaclassGet(%s, %s)" % ( self.bases.result(), - self.mkw.result(), + self.mkw.result()) + else: + code.globalstate.use_utility_code( + UtilityCode.load_cached("CalculateMetaclass", "ObjectHandling.c")) + call = "__Pyx_CalculateMetaclass(NULL, %s)" % ( + self.bases.result()) + code.putln( + "%s = %s; %s" % ( + self.result(), call, code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -6983,6 +6999,10 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin): doc_code = self.doc.result() else: doc_code = '(PyObject *) NULL' + if self.mkw: + mkw = self.mkw.py_result() + else: + mkw = '(PyObject *) NULL' code.putln( "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % ( self.result(), @@ -6990,7 +7010,7 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin): self.bases.result(), cname, qualname, - self.mkw.result(), + mkw, py_mod_name, doc_code, code.error_goto_if_null(self.result(), self.pos))) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 7633775..5310ad4 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -3915,25 +3915,29 @@ class PyClassDefNode(ClassDefNode): "target", "class_cell", "decorators"] decorators = None class_result = None - py3_style_class = False # Python3 style class (bases+kwargs) + is_py3_style_class = False # Python3 style class (kwargs) + metaclass = None + mkw = None - def __init__(self, pos, name, bases, doc, body, decorators = None, - keyword_args = None, starstar_arg = None): + def __init__(self, pos, name, bases, doc, body, decorators=None, + keyword_args=None, starstar_arg=None, force_py3_semantics=False): StatNode.__init__(self, pos) self.name = name self.doc = doc self.body = body self.decorators = decorators + self.bases = bases import ExprNodes if self.doc and Options.docstrings: doc = embed_position(self.pos, self.doc) - doc_node = ExprNodes.StringNode(pos, value = doc) + doc_node = ExprNodes.StringNode(pos, value=doc) else: doc_node = None + + allow_py2_metaclass = not force_py3_semantics if keyword_args or starstar_arg: - self.py3_style_class = True - self.bases = bases - self.metaclass = None + allow_py2_metaclass = False + self.is_py3_style_class = True if keyword_args and not starstar_arg: for i, item in list(enumerate(keyword_args.key_value_pairs))[::-1]: if item.key.value == 'metaclass': @@ -3946,36 +3950,50 @@ class PyClassDefNode(ClassDefNode): del keyword_args.key_value_pairs[i] if starstar_arg: self.mkw = ExprNodes.KeywordArgsNode( - pos, keyword_args = keyword_args and keyword_args.key_value_pairs or [], - starstar_arg = starstar_arg) - elif keyword_args and keyword_args.key_value_pairs: + pos, keyword_args=keyword_args and keyword_args.key_value_pairs or [], + starstar_arg=starstar_arg) + elif keyword_args.key_value_pairs: self.mkw = keyword_args else: - self.mkw = ExprNodes.NullNode(pos) + assert self.metaclass is not None + + if force_py3_semantics or self.bases or self.mkw or self.metaclass: if self.metaclass is None: + if starstar_arg: + # **kwargs may contain 'metaclass' arg + mkdict = self.mkw + else: + mkdict = None self.metaclass = ExprNodes.PyClassMetaclassNode( - pos, mkw = self.mkw, bases = self.bases) - self.dict = ExprNodes.PyClassNamespaceNode(pos, name = name, - doc = doc_node, metaclass = self.metaclass, bases = self.bases, - mkw = self.mkw) - self.classobj = ExprNodes.Py3ClassNode(pos, name = name, - bases = self.bases, dict = self.dict, doc = doc_node, - metaclass = self.metaclass, mkw = self.mkw) + pos, mkw=mkdict, bases=self.bases) + needs_metaclass_calculation = False + else: + needs_metaclass_calculation = True + + self.dict = ExprNodes.PyClassNamespaceNode( + pos, name=name, doc=doc_node, + metaclass=self.metaclass, bases=self.bases, mkw=self.mkw) + self.classobj = ExprNodes.Py3ClassNode( + pos, name=name, + bases=self.bases, dict=self.dict, doc=doc_node, + metaclass=self.metaclass, mkw=self.mkw, + calculate_metaclass=needs_metaclass_calculation, + allow_py2_metaclass=allow_py2_metaclass) else: - self.dict = ExprNodes.DictNode(pos, key_value_pairs = []) - self.metaclass = None - self.mkw = None - self.bases = None - self.classobj = ExprNodes.ClassNode(pos, name = name, - bases = bases, dict = self.dict, doc = doc_node) - self.target = ExprNodes.NameNode(pos, name = name) + # no bases, no metaclass => old style class creation + self.dict = ExprNodes.DictNode(pos, key_value_pairs=[]) + self.classobj = ExprNodes.ClassNode( + pos, name=name, + bases=bases, dict=self.dict, doc=doc_node) + + self.target = ExprNodes.NameNode(pos, name=name) self.class_cell = ExprNodes.ClassCellInjectorNode(self.pos) def as_cclass(self): """ Return this node as if it were declared as an extension class """ - if self.py3_style_class: + if self.is_py3_style_class: error(self.classobj.pos, "Python3 style class could not be represented as C class") return bases = self.classobj.bases.args @@ -4039,9 +4057,11 @@ class PyClassDefNode(ClassDefNode): self.body.analyse_declarations(cenv) def analyse_expressions(self, env): - if self.py3_style_class: + if self.bases: self.bases = self.bases.analyse_expressions(env) + if self.metaclass: self.metaclass = self.metaclass.analyse_expressions(env) + if self.mkw: self.mkw = self.mkw.analyse_expressions(env) self.dict = self.dict.analyse_expressions(env) self.class_result = self.class_result.analyse_expressions(env) @@ -4059,9 +4079,11 @@ class PyClassDefNode(ClassDefNode): def generate_execution_code(self, code): code.pyclass_stack.append(self) cenv = self.scope - if self.py3_style_class: + if self.bases: self.bases.generate_evaluation_code(code) + if self.mkw: self.mkw.generate_evaluation_code(code) + if self.metaclass: self.metaclass.generate_evaluation_code(code) self.dict.generate_evaluation_code(code) cenv.namespace_cname = cenv.class_obj_cname = self.dict.result() @@ -4075,11 +4097,13 @@ class PyClassDefNode(ClassDefNode): self.target.generate_assignment_code(self.class_result, code) self.dict.generate_disposal_code(code) self.dict.free_temps(code) - if self.py3_style_class: - self.mkw.generate_disposal_code(code) - self.mkw.free_temps(code) + if self.metaclass: self.metaclass.generate_disposal_code(code) self.metaclass.free_temps(code) + if self.mkw: + self.mkw.generate_disposal_code(code) + self.mkw.free_temps(code) + if self.bases: self.bases.generate_disposal_code(code) self.bases.free_temps(code) code.pyclass_stack.pop() diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index a77a227..c84c778 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -2962,12 +2962,13 @@ def p_class_statement(s, decorators): # XXX: empty arg_tuple arg_tuple = ExprNodes.TupleNode(pos, args=[]) doc, body = p_suite_with_docstring(s, Ctx(level='class')) - return Nodes.PyClassDefNode(pos, - name = class_name, - bases = arg_tuple, - keyword_args = keyword_dict, - starstar_arg = starstar_arg, - doc = doc, body = body, decorators = decorators) + return Nodes.PyClassDefNode( + pos, name=class_name, + bases=arg_tuple, + keyword_args=keyword_dict, + starstar_arg=starstar_arg, + doc=doc, body=body, decorators=decorators, + force_py3_semantics=s.context.language_level >= 3) def p_c_class_definition(s, pos, ctx): # s.sy == 'class' diff --git a/Cython/Utility/ObjectHandling.c b/Cython/Utility/ObjectHandling.c index d7e3922..3dd9c59 100644 --- a/Cython/Utility/ObjectHandling.c +++ b/Cython/Utility/ObjectHandling.c @@ -663,38 +663,93 @@ static CYTHON_INLINE PyObject* __Pyx_Py{{type}}_GetSlice( {{endfor}} #endif -/////////////// FindPy2Metaclass.proto /////////////// -static PyObject *__Pyx_FindPy2Metaclass(PyObject *bases); /*proto*/ +/////////////// CalculateMetaclass.proto /////////////// -/////////////// FindPy2Metaclass /////////////// +static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases); + +/////////////// CalculateMetaclass /////////////// + +static PyObject *__Pyx_CalculateMetaclass(PyTypeObject *metaclass, PyObject *bases) { + Py_ssize_t i, nbases = PyTuple_GET_SIZE(bases); + for (i=0; i < nbases; i++) { + PyTypeObject *tmptype; + PyObject *tmp = PyTuple_GET_ITEM(bases, i); + tmptype = Py_TYPE(tmp); +#if PY_MAJOR_VERSION < 3 + if (tmptype == &PyClass_Type) + continue; +#endif + if (!metaclass) { + metaclass = tmptype; + continue; + } + if (PyType_IsSubtype(metaclass, tmptype)) + continue; + if (PyType_IsSubtype(tmptype, metaclass)) { + metaclass = tmptype; + continue; + } + // else: + PyErr_SetString(PyExc_TypeError, + "metaclass conflict: " + "the metaclass of a derived class " + "must be a (non-strict) subclass " + "of the metaclasses of all its bases"); + return NULL; + } + if (!metaclass) { +#if PY_MAJOR_VERSION < 3 + metaclass = &PyClass_Type; +#else + metaclass = &PyType_Type; +#endif + } + // make owned reference + Py_INCREF((PyObject*) metaclass); + return (PyObject*) metaclass; +} + + +/////////////// FindInheritedMetaclass.proto /////////////// + +static PyObject *__Pyx_FindInheritedMetaclass(PyObject *bases); /*proto*/ + +/////////////// FindInheritedMetaclass /////////////// //@requires: PyObjectGetAttrStr +//@requires: CalculateMetaclass -static PyObject *__Pyx_FindPy2Metaclass(PyObject *bases) { +static PyObject *__Pyx_FindInheritedMetaclass(PyObject *bases) { PyObject *metaclass; - /* Default metaclass */ -#if PY_MAJOR_VERSION < 3 if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { + PyTypeObject *metatype; PyObject *base = PyTuple_GET_ITEM(bases, 0); - metaclass = __Pyx_PyObject_GetAttrStr(base, PYIDENT("__class__")); - if (!metaclass) { +#if PY_MAJOR_VERSION < 3 + PyObject* basetype = __Pyx_PyObject_GetAttrStr(base, PYIDENT("__class__")); + if (basetype) { + metatype = (PyType_Check(basetype)) ? ((PyTypeObject*) basetype) : NULL; + } else { PyErr_Clear(); - metaclass = (PyObject*) Py_TYPE(base); - Py_INCREF(metaclass); + metatype = Py_TYPE(base); + basetype = (PyObject*) metatype; + Py_INCREF(basetype); } +#else + metatype = Py_TYPE(base); +#endif + metaclass = __Pyx_CalculateMetaclass(metatype, bases); +#if PY_MAJOR_VERSION < 3 + Py_DECREF(basetype); +#endif } else { + // no bases => use default metaclass +#if PY_MAJOR_VERSION < 3 metaclass = (PyObject *) &PyClass_Type; - Py_INCREF(metaclass); - } #else - if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { - PyObject *base = PyTuple_GET_ITEM(bases, 0); - metaclass = (PyObject*) Py_TYPE(base); - } else { metaclass = (PyObject *) &PyType_Type; - } - Py_INCREF(metaclass); #endif + Py_INCREF(metaclass); + } return metaclass; } @@ -703,7 +758,8 @@ static PyObject *__Pyx_FindPy2Metaclass(PyObject *bases) { static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw); /*proto*/ /////////////// Py3MetaclassGet /////////////// -//@requires: FindPy2Metaclass +//@requires: FindInheritedMetaclass +//@requires: CalculateMetaclass static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw) { PyObject *metaclass = PyDict_GetItem(mkw, PYIDENT("metaclass")); @@ -713,9 +769,14 @@ static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw) { Py_DECREF(metaclass); return NULL; } + if (PyType_Check(metaclass)) { + PyObject* orig = metaclass; + metaclass = __Pyx_CalculateMetaclass((PyTypeObject*) metaclass, bases); + Py_DECREF(orig); + } return metaclass; } - return __Pyx_FindPy2Metaclass(bases); + return __Pyx_FindInheritedMetaclass(bases); } /////////////// CreateClass.proto /////////////// @@ -724,7 +785,8 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na PyObject *qualname, PyObject *modname); /*proto*/ /////////////// CreateClass /////////////// -//@requires: FindPy2Metaclass +//@requires: FindInheritedMetaclass +//@requires: CalculateMetaclass static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, PyObject *qualname, PyObject *modname) { @@ -740,9 +802,16 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na metaclass = PyDict_GetItem(dict, PYIDENT("__metaclass__")); if (metaclass) { Py_INCREF(metaclass); + if (PyType_Check(metaclass)) { + PyObject* orig = metaclass; + metaclass = __Pyx_CalculateMetaclass((PyTypeObject*) metaclass, bases); + Py_DECREF(orig); + } } else { - metaclass = __Pyx_FindPy2Metaclass(bases); + metaclass = __Pyx_FindInheritedMetaclass(bases); } + if (unlikely(!metaclass)) + return NULL; result = PyObject_CallFunctionObjArgs(metaclass, name, bases, dict, NULL); Py_DECREF(metaclass); return result; @@ -750,11 +819,14 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na /////////////// Py3ClassCreate.proto /////////////// -static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc); /*proto*/ -static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases, PyObject *dict, PyObject *mkw); /*proto*/ +static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname, + PyObject *mkw, PyObject *modname, PyObject *doc); /*proto*/ +static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases, PyObject *dict, + PyObject *mkw, int calculate_metaclass, int allow_py2_metaclass); /*proto*/ /////////////// Py3ClassCreate /////////////// //@requires: PyObjectGetAttrStr +//@requires: CalculateMetaclass static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc) { @@ -764,27 +836,28 @@ static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, prep = __Pyx_PyObject_GetAttrStr(metaclass, PYIDENT("__prepare__")); if (!prep) { - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + if (unlikely(!PyErr_ExceptionMatches(PyExc_AttributeError))) return NULL; PyErr_Clear(); - return PyDict_New(); - } - pargs = PyTuple_Pack(2, name, bases); - if (!pargs) { + ns = PyDict_New(); + } else { + pargs = PyTuple_Pack(2, name, bases); + if (unlikely(!pargs)) { + Py_DECREF(prep); + return NULL; + } + ns = PyObject_Call(prep, pargs, mkw); Py_DECREF(prep); - return NULL; + Py_DECREF(pargs); } - ns = PyObject_Call(prep, pargs, mkw); - Py_DECREF(prep); - Py_DECREF(pargs); - if (ns == NULL) + if (unlikely(!ns)) return NULL; /* Required here to emulate assignment order */ - if (PyObject_SetItem(ns, PYIDENT("__module__"), modname) < 0) goto bad; - if (PyObject_SetItem(ns, PYIDENT("__qualname__"), qualname) < 0) goto bad; - if (doc && PyObject_SetItem(ns, PYIDENT("__doc__"), doc) < 0) goto bad; + if (unlikely(PyObject_SetItem(ns, PYIDENT("__module__"), modname) < 0)) goto bad; + if (unlikely(PyObject_SetItem(ns, PYIDENT("__qualname__"), qualname) < 0)) goto bad; + if (unlikely(doc && PyObject_SetItem(ns, PYIDENT("__doc__"), doc) < 0)) goto bad; return ns; bad: Py_DECREF(ns); @@ -792,13 +865,48 @@ bad: } static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases, - PyObject *dict, PyObject *mkw) { - PyObject *result; - PyObject *margs = PyTuple_Pack(3, name, bases, dict); - if (!margs) - return NULL; - result = PyObject_Call(metaclass, margs, mkw); - Py_DECREF(margs); + PyObject *dict, PyObject *mkw, + int calculate_metaclass, int allow_py2_metaclass) { + PyObject *result, *margs; + PyObject *py2_metaclass = NULL; + if (allow_py2_metaclass) { + /* honour Python2 __metaclass__ for backward compatibility */ + py2_metaclass = PyObject_GetItem(dict, PYIDENT("__metaclass__")); + if (py2_metaclass) { + if (likely(PyType_Check(py2_metaclass))) { + metaclass = py2_metaclass; + calculate_metaclass = 1; + } else { + /* py2_metaclass != NULL => calculate_metaclass != 0 */ + Py_DECREF(py2_metaclass); + py2_metaclass = NULL; + } + } else if (likely(PyErr_ExceptionMatches(PyExc_KeyError))) { + PyErr_Clear(); + } else { + return NULL; + } + } + if (calculate_metaclass) { + if (py2_metaclass || PyType_Check(metaclass)) { + metaclass = __Pyx_CalculateMetaclass((PyTypeObject*) metaclass, bases); + Py_XDECREF(py2_metaclass); + if (unlikely(!metaclass)) + return NULL; + } else { + Py_XDECREF(py2_metaclass); + calculate_metaclass = 0; + } + } + margs = PyTuple_Pack(3, name, bases, dict); + if (unlikely(!margs)) { + result = NULL; + } else { + result = PyObject_Call(metaclass, margs, mkw); + Py_DECREF(margs); + } + if (calculate_metaclass) + Py_DECREF(metaclass); return result; } diff --git a/tests/run/locals_T732.pyx b/tests/run/locals_T732.pyx index 85e884f..aa5b1ad 100644 --- a/tests/run/locals_T732.pyx +++ b/tests/run/locals_T732.pyx @@ -24,7 +24,7 @@ def test_class_locals_and_dir(): >>> 'visible' in klass.locs and 'not_visible' not in klass.locs True >>> klass.names - ['visible'] + ['__module__', '__qualname__', 'visible'] """ not_visible = 1234 class Foo: diff --git a/tests/run/metaclass.pyx b/tests/run/metaclass.pyx index 81c7a7a..d057486 100644 --- a/tests/run/metaclass.pyx +++ b/tests/run/metaclass.pyx @@ -6,7 +6,7 @@ class Base(type): attrs['metaclass_was_here'] = True return type.__new__(cls, name, bases, attrs) -@cython.test_fail_if_path_exists("//PyClassMetaclassNode", "//Py3ClassNode") +@cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode") class Foo(object): """ >>> obj = Foo() @@ -27,6 +27,7 @@ class ODict(dict): class Py3MetaclassPlusAttr(type): def __new__(cls, name, bases, attrs, **kwargs): + assert isinstance(attrs, ODict), str(type(attrs)) for key, value in kwargs.items(): attrs[key] = value attrs['metaclass_was_here'] = True @@ -53,8 +54,21 @@ class Py3ClassMCOnly(object, metaclass=Py3MetaclassPlusAttr): """ bar = 321 +class Py3InheritedMetaclass(Py3ClassMCOnly): + """ + >>> obj = Py3InheritedMetaclass() + >>> obj.bar + 345 + >>> obj.metaclass_was_here + True + >>> obj._order + ['__module__', '__qualname__', '__doc__', 'bar', 'metaclass_was_here'] + """ + bar = 345 + class Py3Base(type): def __new__(cls, name, bases, attrs, **kwargs): + assert isinstance(attrs, ODict), str(type(attrs)) for key, value in kwargs.items(): attrs[key] = value return type.__new__(cls, name, bases, attrs) @@ -80,6 +94,19 @@ class Py3Foo(object, metaclass=Py3Base, foo=123): """ bar = 321 +@cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode") +class Py3FooInherited(Py3Foo, foo=567): + """ + >>> obj = Py3FooInherited() + >>> obj.foo + 567 + >>> obj.bar + 321 + >>> obj._order + ['__module__', '__qualname__', '__doc__', 'bar', 'foo'] + """ + bar = 321 + kwargs = {'foo': 123, 'bar': 456} @cython.test_assert_path_exists("//PyClassMetaclassNode", "//Py3ClassNode") -- 2.7.4