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
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()``.
# 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 = []
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())
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())
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(),
self.bases.result(),
cname,
qualname,
- self.mkw.result(),
+ mkw,
py_mod_name,
doc_code,
code.error_goto_if_null(self.result(), self.pos)))
"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':
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
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)
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()
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()
# 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'
{{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;
}
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"));
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 ///////////////
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) {
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;
/////////////// 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) {
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);
}
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;
}
>>> 'visible' in klass.locs and 'not_visible' not in klass.locs
True
>>> klass.names
- ['visible']
+ ['__module__', '__qualname__', 'visible']
"""
not_visible = 1234
class Foo:
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()
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
"""
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)
"""
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")