From 54031cb0ce74d5929aa2e9f3bbb4fa04a6827319 Mon Sep 17 00:00:00 2001 From: Vitja Makarov Date: Wed, 3 Nov 2010 01:32:09 +0300 Subject: [PATCH] __metaclass__ support 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 | 73 +++++++++++++++++++++++++++++++----- Cython/Compiler/Nodes.py | 14 +++++-- Cython/Compiler/Symtab.py | 10 ++++- tests/run/classmethod.pyx | 12 ++++++ tests/run/metaclass.pyx | 12 ++++++ tests/run/staticmethod.pyx | 7 ++++ 6 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 tests/run/metaclass.pyx diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 4d8f3ed1..af17c28f 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -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; diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 92536a7a..be840f92 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -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 == '': 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) diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index b229ee27..03d74167 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -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; } diff --git a/tests/run/classmethod.pyx b/tests/run/classmethod.pyx index 863d18b8..e001d23b 100644 --- a/tests/run/classmethod.pyx +++ b/tests/run/classmethod.pyx @@ -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 index 00000000..78b3e40b --- /dev/null +++ b/tests/run/metaclass.pyx @@ -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 diff --git a/tests/run/staticmethod.pyx b/tests/run/staticmethod.pyx index e8004b99..0bc9c05a 100644 --- a/tests/run/staticmethod.pyx +++ b/tests/run/staticmethod.pyx @@ -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 -- 2.26.2