__metaclass__ support
authorVitja Makarov <vitja.makarov@gmail.com>
Tue, 2 Nov 2010 22:32:09 +0000 (01:32 +0300)
committerVitja Makarov <vitja.makarov@gmail.com>
Tue, 2 Nov 2010 22:32:09 +0000 (01:32 +0300)
Fill class attributes dict before class creation. Use bindings for class methods.
And use PyCFunction for staticmethods and __new__. Add simple testcase for __metaclass__.
And test for staticmethod as decorator

Cython/Compiler/ExprNodes.py
Cython/Compiler/Nodes.py
Cython/Compiler/Symtab.py
tests/run/classmethod.pyx
tests/run/metaclass.pyx [new file with mode: 0644]
tests/run/staticmethod.pyx

index 4d8f3ed17af790a538e6df16231b6a614816be16..af17c28f415b04bb5fdade71775f1216bc65d515 100755 (executable)
@@ -1450,6 +1450,22 @@ class NameNode(AtomicExprNode):
             return # There was an error earlier
         if entry.is_builtin and Options.cache_builtins:
             return # Lookup already cached
+        elif entry.is_real_dict:
+            assert entry.type.is_pyobject, "Python global or builtin not a Python object"
+            interned_cname = code.intern_identifier(self.entry.name)
+            if entry.is_builtin:
+                namespace = Naming.builtins_cname
+            else: # entry.is_pyglobal
+                namespace = entry.scope.namespace_cname
+            code.globalstate.use_utility_code(getitem_dict_utility_code)
+            code.putln(
+                '%s = __Pyx_PyDict_GetItem(%s, %s); %s' % (
+                self.result(),
+                namespace,
+                interned_cname,
+                code.error_goto_if_null(self.result(), self.pos)))
+            code.put_gotref(self.py_result())
+            
         elif entry.is_pyglobal or entry.is_builtin:
             assert entry.type.is_pyobject, "Python global or builtin not a Python object"
             interned_cname = code.intern_identifier(self.entry.name)
@@ -1504,8 +1520,16 @@ class NameNode(AtomicExprNode):
                 rhs.free_temps(code)
                 # in Py2.6+, we need to invalidate the method cache
                 code.putln("PyType_Modified(%s);" %
-                           entry.scope.parent_type.typeptr_cname)
-            else: 
+                            entry.scope.parent_type.typeptr_cname)
+            elif entry.is_real_dict:
+                code.put_error_if_neg(self.pos,
+                    'PyDict_SetItem(%s, %s, %s)' % (
+                        namespace,
+                        interned_cname,
+                        rhs.py_result()))
+                rhs.generate_disposal_code(code)
+                rhs.free_temps(code)
+            else:
                 code.put_error_if_neg(self.pos,
                     'PyObject_SetAttr(%s, %s, %s)' % (
                         namespace,
@@ -1584,10 +1608,17 @@ class NameNode(AtomicExprNode):
         if not self.entry.is_pyglobal:
             error(self.pos, "Deletion of local or C global name not supported")
             return
-        code.put_error_if_neg(self.pos, 
-            '__Pyx_DelAttrString(%s, "%s")' % (
-                Naming.module_cname,
-                self.entry.name))
+        if self.entry.is_real_dict:
+            namespace = self.entry.scope.namespace_cname
+            code.put_error_if_neg(self.pos,
+                'PyDict_DelItemString(%s, "%s")' % (
+                    namespace,
+                    self.entry.name))
+        else:
+            code.put_error_if_neg(self.pos, 
+                '__Pyx_DelAttrString(%s, "%s")' % (
+                    Naming.module_cname,
+                    self.entry.name))
                 
     def annotate(self, code):
         if hasattr(self, 'is_called') and self.is_called:
@@ -7127,16 +7158,38 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na
 """,
 impl = """
 static PyObject *__Pyx_CreateClass(
-    PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname)
+    PyObject *bases, PyObject *methods, PyObject *name, PyObject *modname)
 {
     PyObject *result = 0;
+#if PY_MAJOR_VERSION < 3
+    PyObject *metaclass = 0, *base;
+#endif
 
-    if (PyDict_SetItemString(dict, "__module__", modname) < 0)
+    if (PyDict_SetItemString(methods, "__module__", modname) < 0)
         goto bad;
     #if PY_MAJOR_VERSION < 3
-    result = PyClass_New(bases, dict, name);
+    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;
+            Py_INCREF(metaclass);
+        }
+    }
+    else {
+        metaclass = (PyObject *) &PyClass_Type;
+        Py_INCREF(metaclass);
+    }
+
+    result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
     #else
-    result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, dict, NULL);
+    /* it seems that python3+ handle __metaclass__ itself */
+    result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, methods, NULL);
     #endif
 bad:
     return result;
index 92536a7a3aac4c96299bf8d27aca0ef2000084b6..be840f923e834e1d5841191fd28bcbc857c13263 100644 (file)
@@ -1959,6 +1959,9 @@ class DefNode(FuncDefNode):
             # staticmethod() was overridden - not much we can do here ...
             self.is_staticmethod = False
 
+        if self.name == '__new__':
+            self.is_staticmethod = 1
+
         self.analyse_argument_types(env)
         if self.name == '<lambda>':
             self.declare_lambda_function(env)
@@ -2203,9 +2206,11 @@ class DefNode(FuncDefNode):
     def synthesize_assignment_node(self, env):
         import ExprNodes
         if env.is_py_class_scope:
-            rhs = ExprNodes.UnboundMethodNode(self.pos, 
-                function = ExprNodes.PyCFunctionNode(self.pos,
-                    pymethdef_cname = self.entry.pymethdef_cname))
+            rhs = ExprNodes.PyCFunctionNode(self.pos,
+                        pymethdef_cname = self.entry.pymethdef_cname)
+            if not self.is_staticmethod and not self.is_classmethod:
+                rhs.binding = True
+
         elif env.is_closure_scope:
             rhs = ExprNodes.InnerFunctionNode(
                 self.pos, pymethdef_cname = self.entry.pymethdef_cname)
@@ -3016,9 +3021,10 @@ class PyClassDefNode(ClassDefNode):
         code.pyclass_stack.append(self)
         cenv = self.scope
         self.dict.generate_evaluation_code(code)
+        cenv.namespace_cname = cenv.class_obj_cname = self.dict.result()
+        self.body.generate_execution_code(code)
         self.classobj.generate_evaluation_code(code)
         cenv.namespace_cname = cenv.class_obj_cname = self.classobj.result()
-        self.body.generate_execution_code(code)
         self.target.generate_assignment_code(self.classobj, code)
         self.dict.generate_disposal_code(code)
         self.dict.free_temps(code)
index b229ee27ad19b4fdddb81e547a6919a155eb671c..03d74167464794ed4be30e585eecc0313fb8289f 100644 (file)
@@ -70,6 +70,7 @@ class Entry(object):
     #                               or class attribute during
     #                               class construction
     # is_member        boolean    Is an assigned class member
+    # is_real_dict     boolean    Is a real dict, PyClass attributes dict
     # is_variable      boolean    Is a variable
     # is_cfunction     boolean    Is a C function
     # is_cmethod       boolean    Is a C method of an extension type
@@ -131,6 +132,7 @@ class Entry(object):
     is_cglobal = 0
     is_pyglobal = 0
     is_member = 0
+    is_real_dict = 0
     is_variable = 0
     is_cfunction = 0
     is_cmethod = 0
@@ -1405,6 +1407,7 @@ class PyClassScope(ClassScope):
         entry = Scope.declare_var(self, name, type, pos, 
             cname, visibility, is_cdef)
         entry.is_pyglobal = 1
+        entry.is_real_dict = 1
         return entry
 
     def add_default_value(self, type):
@@ -1768,8 +1771,13 @@ static PyObject* __Pyx_Method_ClassMethod(PyObject *method) {
     else if (PyCFunction_Check(method)) {
         return PyClassMethod_New(method);
     }
+#ifdef __pyx_binding_PyCFunctionType_USED
+    else if (PyObject_TypeCheck(method, __pyx_binding_PyCFunctionType)) { /* binded CFunction */
+        return PyClassMethod_New(method);
+    }
+#endif
     PyErr_Format(PyExc_TypeError,
-                 "Class-level classmethod() can only be called on"
+                 "Class-level classmethod() can only be called on "
                  "a method_descriptor or instance method.");
     return NULL;
 }
index 863d18b8f45a0150b6de8b3297534a6a50c7d774..e001d23b48eb5132e68c23f5796b339727e11d87 100644 (file)
@@ -11,6 +11,10 @@ class2
 class3
 >>> class3.plus(1)
 8
+>>> class4.view()
+class4
+>>> class5.view()
+class5
 """
 
 def f_plus(cls, a):
@@ -36,3 +40,11 @@ cdef class class3:
     def view(cls):
         print cls.__name__
     view = classmethod(view)
+
+class class4:
+    @classmethod
+    def view(cls):
+        print cls.__name__
+
+class class5(class4):
+    pass
diff --git a/tests/run/metaclass.pyx b/tests/run/metaclass.pyx
new file mode 100644 (file)
index 0000000..78b3e40
--- /dev/null
@@ -0,0 +1,12 @@
+"""
+>>> obj = Foo()
+>>> obj.metaclass_was_here
+True
+"""
+class Base(type):
+    def __new__(cls, name, bases, attrs):
+        attrs['metaclass_was_here'] = True
+        return type.__new__(cls, name, bases, attrs)
+
+class Foo(object):
+    __metaclass__ = Base
index e8004b99a817d87b9d535a13b60125c94bc59ae9..0bc9c05a69c2524b01d522fade80cb2bc3a67fdd 100644 (file)
@@ -5,6 +5,8 @@ __doc__ = u"""
 2
 >>> class3.plus1(1)
 2
+>>> class4.plus1(1)
+2
 """
 
 def f_plus(a):
@@ -18,3 +20,8 @@ class class2(object):
 
 cdef class class3:
     plus1 = f_plus
+
+class class4:
+    @staticmethod
+    def plus1(a):
+        return a + 1