Here is my first try with metaclasses in py3. It does not handle
__prepare__ I don't understand what function should I call to create
cell... and what is that func
2010/11/4 Stefan Behnel <[email protected]>:
> Vitja Makarov, 03.11.2010 23:03:
>> 2010/11/4 Stefan Behnel:
>>> Vitja Makarov, 03.11.2010 22:13:
>>>> Now it seems to me that class declaration is more like function call
>>>> with special case for metaclass keyword and positional arguments:
>>>>
>>>> class Base(type):
>>>> def __new__(cls, name, bases, attrs, **kwargs):
>>>> print(cls, name, bases, attrs, kwargs)
>>>>
>>>> class Foo(1, 2, 3, metaclass=Base, foo='bar'):
>>>> def hello(self):
>>>> pass
>>>
>>> Yes, I think that's exactly how this should work. Want to give it another
>>> try?
>>
>> Yes, I want to finish this part ;)
>
> Great! :)
>
> BTW, thanks for doing all this. Even your first patch was pretty good for a
> "first patch".
>
>
>>> It seems that the number of positional arguments is fixed, but you can pass
>>> any number of keyword arguments, out of which only "metaclass" is special
>>> cased and removed (from a copy of the dict, BTW). There is also an
>>> additional protocol if the metaclass has a "__prepare__" class method. See
>>> "__build_class__".
>>
>> Yes, all positional arguments are turned into bases. So we can make it this
>> way:
>>
>> Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name,
>> PyObject *modname, PyObject *kwargs);
>>
>> And then
>>
>> args = (metaclass, bases, name, dict)
>> PyEval_CallObjectWithKeywords(metaclass, args, kwargs)
>
> Except for the first 'metaclass' in args (args has length 3). Again, see
> the __build_class__ implementation.
>
>
>>> I think the Python 2 protocol in Cython should work as in Py3. Metaclasses
>>> in Py2 won't generally support kwargs, but if a user explicitly provides
>>> them, it's best to assume that that's intentional. In the worst case, there
>>> will be a runtime TypeError.
>>
>> Do you mean that new syntax should be used for Py2?
>
> No, just what happens behind the syntax. A __metaclass__ field can easily
> be mapped to a metaclass keyword argument (or the equivalent representation
> on a PyClassDefNode in Cython) and from that point on, it's the same thing
> in both cases.
>
>
>>> So, kwargs should pass through, except for __metaclass__. If both the
>>> __metaclass__ class field and the metaclass kwarg are passed, I'd lean
>>> towards ignoring the field and prefer the kwarg, just like Py3 does. It can
>>> be argued that this is worth a warning, though.
>>>
>> Py3 doesn't support __metaclass__ attribute, so if metaclass is set that
>> seems to be mostly Py3 so __metaclass__ should be ignored.
>
> If I'm not mistaken, the __metaclass__ field can always be handled by the
> compiler during type analysis (or maybe in a transform, see the classes in
> ParseTreeTransforms.py, for example). You can't dynamically add an
> attribute to a class at definition time without letting the parser see its
> name. So you just have to check the class body and you'll know if the field
> is there or not.
>
> Cython also has a Python 3 syntax mode (look out for "language_level") in
> which we can disable this field lookup, so that you get semantic
> equivalence with Python 3 even before the code generation phase. So we
> don't need any runtime support for the __metaclass__ field, not even in Py2.
>
>
>>> I also think that the parser should extract the metaclass keyword, not the
>>> runtime code. So, if provided, it should be passed into __Pyx_CreateClass()
>>> as an argument and not in the kwargs.
>>
>> I think parser can't extract metaclass keyword, try this code:
>>
>> class Base(type):
>> def __new__(cls, name, bases, attrs, **kwargs):
>> print(cls, name, bases, attrs, kwargs)
>>
>> myargs={'metaclass': Base}
>> class Foo(1, 2, 3, **myargs):
>> def hello(self):
>> pass
>
> Well, it can if it's passed explicitly, though.
>
> We already need to support an explicit metaclass to handle the
> __metaclass__ field. In most cases, the keyword argument will be spelled
> out explicitly, so we can reduce the runtime overhead in that case. But
> that sounds like an optimisation, not a required feature.
>
> Stefan
> _______________________________________________
> Cython-dev mailing list
> [email protected]
> http://codespeak.net/mailman/listinfo/cython-dev
>
diff -r 4b5abd81bb75 Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py Wed Nov 03 21:44:55 2010 +0100
+++ b/Cython/Compiler/ExprNodes.py Thu Nov 04 14:13:02 2010 +0300
@@ -4475,14 +4475,23 @@
# dict ExprNode Class dict (not owned by this node)
# doc ExprNode or None Doc string
# module_name EncodedString Name of defining module
-
- subexprs = ['bases', 'doc']
+ # keyword_args ExprNode or None Py3 Dict of keyword arguments, passed to __new__
+ # starstar_arg ExprNode or None Py3 Dict of extra keyword args, same here
+
+ subexprs = ['bases', 'keyword_args', 'starstar_arg', 'doc']
def analyse_types(self, env):
self.bases.analyse_types(env)
if self.doc:
self.doc.analyse_types(env)
self.doc = self.doc.coerce_to_pyobject(env)
+ if self.keyword_args:
+ self.keyword_args.analyse_types(env)
+ if self.starstar_arg:
+ self.starstar_arg.analyse_types(env)
+ if self.starstar_arg:
+ self.starstar_arg = \
+ self.starstar_arg.coerce_to_pyobject(env)
self.type = py_object_type
self.is_temp = 1
env.use_utility_code(create_class_utility_code);
@@ -4496,6 +4505,19 @@
def generate_result_code(self, code):
cname = code.intern_identifier(self.name)
+ if self.keyword_args and self.starstar_arg:
+ code.put_error_if_neg(self.pos,
+ "PyDict_Update(%s, %s)" % (
+ self.keyword_args.py_result(),
+ self.starstar_arg.py_result()))
+ keyword_code = self.keyword_args.py_result()
+ elif self.keyword_args:
+ keyword_code = self.keyword_args.py_result()
+ elif self.starstar_arg:
+ keyword_code = self.starstar_arg.py_result()
+ else:
+ keyword_code = 'NULL'
+
if self.doc:
code.put_error_if_neg(self.pos,
'PyDict_SetItemString(%s, "__doc__", %s)' % (
@@ -4503,12 +4525,13 @@
self.doc.py_result()))
py_mod_name = self.get_py_mod_name(code)
code.putln(
- '%s = __Pyx_CreateClass(%s, %s, %s, %s); %s' % (
+ '%s = __Pyx_CreateClass(%s, %s, %s, %s, %s); %s' % (
self.result(),
self.bases.py_result(),
self.dict.py_result(),
cname,
py_mod_name,
+ keyword_code,
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
@@ -7154,44 +7177,73 @@
create_class_utility_code = UtilityCode(
proto = """
-static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname); /*proto*/
+static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name,
+ PyObject *modname, PyObject *kwargs); /*proto*/
""",
impl = """
static PyObject *__Pyx_CreateClass(
- PyObject *bases, PyObject *methods, PyObject *name, PyObject *modname)
+ PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname, PyObject *kwargs)
{
- PyObject *result = 0;
-#if PY_MAJOR_VERSION < 3
- PyObject *metaclass = 0, *base;
-#endif
-
- if (PyDict_SetItemString(methods, "__module__", modname) < 0)
+ PyObject *result = NULL;
+ PyObject *metaclass = NULL;
+
+ if (PyDict_SetItemString(dict, "__module__", modname) < 0)
goto bad;
- #if PY_MAJOR_VERSION < 3
- metaclass = PyDict_GetItemString(methods, "__metaclass__");
-
- if (metaclass != NULL)
- Py_INCREF(metaclass);
- else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
- base = PyTuple_GET_ITEM(bases, 0);
- metaclass = PyObject_GetAttrString(base, "__class__");
- if (metaclass == NULL) {
- PyErr_Clear();
- metaclass = (PyObject *)base->ob_type;
+
+ /* Python3 metaclasses */
+ if (kwargs) {
+ metaclass = PyDict_GetItemString(kwargs, "metaclass");
+ if (metaclass) {
Py_INCREF(metaclass);
+ /* it is safe to modify kwargs here */
+ if (PyDict_DelItemString(kwargs, "metaclass") < 0) {
+ Py_DECREF(metaclass);
+ goto bad;
+ }
}
}
- else {
- metaclass = (PyObject *) &PyClass_Type;
+
+ /* Python2 metaclasses */
+ if (metaclass == NULL) {
+ metaclass = PyDict_GetItemString(dict, "__metaclass__");
+ if (metaclass)
+ Py_INCREF(metaclass);
+ }
+
+ if (metaclass == NULL) {
+#if PY_MAJOR_VERSION < 3
+ if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
+ PyObject *base = PyTuple_GET_ITEM(bases, 0);
+ metaclass = PyObject_GetAttrString(base, "__class__");
+ if (metaclass == NULL) {
+ PyErr_Clear();
+ metaclass = (PyObject *)base->ob_type;
+ }
+ } else {
+ metaclass = (PyObject *) &PyClass_Type;
+ }
+#else
+ if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
+ PyObject *base = PyTuple_GET_ITEM(bases, 0);
+ metaclass = (PyObject *)base->ob_type;
+ } else {
+ metaclass = (PyObject *) &PyType_Type;
+ }
+
+#endif
Py_INCREF(metaclass);
}
- result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
+ if (kwargs && PyDict_Size(kwargs) > 0) {
+ PyObject *margs;
+ margs = PyTuple_Pack(3, name, bases, dict, NULL);
+ result = PyEval_CallObjectWithKeywords(metaclass, margs, kwargs);
+ Py_DECREF(margs);
+ } else {
+ result = PyObject_CallFunctionObjArgs(metaclass, name, bases, dict, NULL);
+ }
+
Py_DECREF(metaclass);
- #else
- /* it seems that python3+ handle __metaclass__ itself */
- result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, methods, NULL);
- #endif
bad:
return result;
}
diff -r 4b5abd81bb75 Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py Wed Nov 03 21:44:55 2010 +0100
+++ b/Cython/Compiler/Nodes.py Thu Nov 04 14:13:02 2010 +0300
@@ -2935,7 +2935,8 @@
child_attrs = ["body", "dict", "classobj", "target"]
decorators = None
- def __init__(self, pos, name, bases, doc, body, decorators = None):
+ def __init__(self, pos, name, bases, doc, body, decorators = None,
+ keyword_args = None, starstar_arg = None):
StatNode.__init__(self, pos)
self.name = name
self.doc = doc
@@ -2950,7 +2951,8 @@
else:
doc_node = None
self.classobj = ExprNodes.ClassNode(pos, name = name,
- bases = bases, dict = self.dict, doc = doc_node)
+ bases = bases, dict = self.dict, doc = doc_node,
+ keyword_args = keyword_args, starstar_arg = starstar_arg)
self.target = ExprNodes.NameNode(pos, name = name)
def as_cclass(self):
diff -r 4b5abd81bb75 Cython/Compiler/Parsing.py
--- a/Cython/Compiler/Parsing.py Wed Nov 03 21:44:55 2010 +0100
+++ b/Cython/Compiler/Parsing.py Thu Nov 04 14:13:02 2010 +0300
@@ -381,7 +381,7 @@
# arglist: argument (',' argument)* [',']
# argument: [test '='] test # Really [keyword '='] test
-def p_call(s, function):
+def p_call_parse(s):
# s.sy == '('
pos = s.position()
s.next()
@@ -428,29 +428,43 @@
if s.sy == ',':
s.next()
s.expect(')')
+ return positional_args, keyword_args, star_arg, starstar_arg
+
+def p_call_prepare_full(pos, positional_args, keyword_args, star_arg):
+ arg_tuple = None
+ keyword_dict = None
+ if positional_args or not star_arg:
+ arg_tuple = ExprNodes.TupleNode(pos,
+ args = positional_args)
+ if star_arg:
+ star_arg_tuple = ExprNodes.AsTupleNode(pos, arg = star_arg)
+ if arg_tuple:
+ arg_tuple = ExprNodes.binop_node(pos,
+ operator = '+', operand1 = arg_tuple,
+ operand2 = star_arg_tuple)
+ else:
+ arg_tuple = star_arg_tuple
+ if keyword_args:
+ keyword_args = [ExprNodes.DictItemNode(pos=key.pos, key=key, value=value)
+ for key, value in keyword_args]
+ keyword_dict = ExprNodes.DictNode(pos,
+ key_value_pairs = keyword_args)
+ return arg_tuple, keyword_dict
+
+def p_call(s, function):
+ # s.sy == '('
+ pos = s.position()
+
+ positional_args, keyword_args, star_arg, starstar_arg = \
+ p_call_parse(s)
+
if not (keyword_args or star_arg or starstar_arg):
return ExprNodes.SimpleCallNode(pos,
function = function,
args = positional_args)
else:
- arg_tuple = None
- keyword_dict = None
- if positional_args or not star_arg:
- arg_tuple = ExprNodes.TupleNode(pos,
- args = positional_args)
- if star_arg:
- star_arg_tuple = ExprNodes.AsTupleNode(pos, arg = star_arg)
- if arg_tuple:
- arg_tuple = ExprNodes.binop_node(pos,
- operator = '+', operand1 = arg_tuple,
- operand2 = star_arg_tuple)
- else:
- arg_tuple = star_arg_tuple
- if keyword_args:
- keyword_args = [ExprNodes.DictItemNode(pos=key.pos, key=key, value=value)
- for key, value in keyword_args]
- keyword_dict = ExprNodes.DictNode(pos,
- key_value_pairs = keyword_args)
+ arg_tuple, keyword_dict = p_call_prepare_full(pos,
+ positional_args, keyword_args, star_arg)
return ExprNodes.GeneralCallNode(pos,
function = function,
positional_args = arg_tuple,
@@ -2607,16 +2621,23 @@
s.next()
class_name = EncodedString( p_ident(s) )
class_name.encoding = s.source_encoding
+ arg_tuple = None
+ keyword_dict = None
+ starstar_arg = None
if s.sy == '(':
- s.next()
- base_list = p_simple_expr_list(s)
- s.expect(')')
- else:
- base_list = []
+ positional_args, keyword_args, star_arg, starstar_arg = \
+ p_call_parse(s)
+ arg_tuple, keyword_dict = p_call_prepare_full(pos,
+ positional_args, keyword_args, star_arg)
+ if arg_tuple is None:
+ # XXX: empty arg_tuple
+ arg_tuple = ExprNodes.TupleNode(pos, args = [])
doc, body = p_suite(s, Ctx(level = 'class'), with_doc = 1)
return Nodes.PyClassDefNode(pos,
name = class_name,
- bases = ExprNodes.TupleNode(pos, args = base_list),
+ bases = arg_tuple,
+ keyword_args = keyword_dict,
+ starstar_arg = starstar_arg,
doc = doc, body = body, decorators = decorators)
def p_c_class_definition(s, pos, ctx):
diff -r 4b5abd81bb75 tests/run/metaclass.pyx
--- a/tests/run/metaclass.pyx Wed Nov 03 21:44:55 2010 +0100
+++ b/tests/run/metaclass.pyx Thu Nov 04 14:13:02 2010 +0300
@@ -11,3 +11,23 @@
True
"""
__metaclass__ = Base
+
+class Py3Base(type):
+ def __new__(cls, name, bases, attrs, foo=None):
+ attrs['foo'] = foo
+ return type.__new__(cls, name, bases, attrs)
+
+ def __init__(self, cls, attrs, obj, foo=None):
+ super(Py3Base, self).__init__(cls, attrs, obj)
+# NOT YET SUPPORTED
+# @staticmethod
+# def __prepare__(name, bases, **kwargs):
+# return {'bar': 666}
+
+class Py3Foo(object, metaclass=Py3Base, foo=123):
+ """
+ >>> obj = Py3Foo()
+ >>> obj.foo
+ 123
+ """
+ zzz = 111
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev