Skip to content

Commit

Permalink
__metaclass__ support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vitek committed Nov 2, 2010
1 parent 581671b commit 54031cb
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 15 deletions.
73 changes: 63 additions & 10 deletions Cython/Compiler/ExprNodes.py
Expand Up @@ -1450,6 +1450,22 @@ def generate_result_code(self, code):
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)
Expand Down Expand Up @@ -1504,8 +1520,16 @@ def generate_assignment_code(self, rhs, code):
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,
Expand Down Expand Up @@ -1584,10 +1608,17 @@ def generate_deletion_code(self, code):
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:
Expand Down Expand Up @@ -7127,16 +7158,38 @@ def generate_result_code(self, code):
""",
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;
Expand Down
14 changes: 10 additions & 4 deletions Cython/Compiler/Nodes.py
Expand Up @@ -1959,6 +1959,9 @@ def analyse_declarations(self, env):
# 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)
Expand Down Expand Up @@ -2203,9 +2206,11 @@ def needs_assignment_synthesis(self, env, code=None):
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)
Expand Down Expand Up @@ -3016,9 +3021,10 @@ def generate_execution_code(self, code):
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)
Expand Down
10 changes: 9 additions & 1 deletion Cython/Compiler/Symtab.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1405,6 +1407,7 @@ def declare_var(self, name, type, pos,
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):
Expand Down Expand Up @@ -1768,8 +1771,13 @@ def declare_pyfunction(self, name, pos):
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;
}
Expand Down
12 changes: 12 additions & 0 deletions tests/run/classmethod.pyx
Expand Up @@ -11,6 +11,10 @@ class2
class3
>>> class3.plus(1)
8
>>> class4.view()
class4
>>> class5.view()
class5
"""

def f_plus(cls, a):
Expand All @@ -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
12 changes: 12 additions & 0 deletions 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
7 changes: 7 additions & 0 deletions tests/run/staticmethod.pyx
Expand Up @@ -5,6 +5,8 @@ __doc__ = u"""
2
>>> class3.plus1(1)
2
>>> class4.plus1(1)
2
"""

def f_plus(a):
Expand All @@ -18,3 +20,8 @@ class class2(object):

cdef class class3:
plus1 = f_plus

class class4:
@staticmethod
def plus1(a):
return a + 1

0 comments on commit 54031cb

Please sign in to comment.