Commit 5efe391e authored by empyrical's avatar empyrical

Add special __dict__ attribute to extension types

parent bfd2e433
......@@ -6432,8 +6432,8 @@ class AttributeNode(ExprNode):
return
self.entry = entry
if entry:
if obj_type.is_extension_type and entry.name == "__weakref__":
error(self.pos, "Illegal use of special attribute __weakref__")
if obj_type.is_extension_type and entry.name in ("__weakref__", "__dict__"):
error(self.pos, "Illegal use of special attribute %s" % entry.name)
# def methods need the normal attribute lookup
# because they do not have struct entries
......
......@@ -1152,6 +1152,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
self.generate_descr_get_function(scope, code)
if scope.defines_any(["__set__", "__delete__"]):
self.generate_descr_set_function(scope, code)
if scope.lookup_here("__dict__"):
self.generate_dict_getter(scope, code)
self.generate_property_accessors(scope, code)
self.generate_method_table(scope, code)
self.generate_getset_table(scope, code)
......@@ -1323,11 +1325,15 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if weakref_slot not in scope.var_entries:
weakref_slot = None
dict_slot = scope.lookup_here("__dict__")
if dict_slot not in scope.var_entries:
dict_slot = None
_, (py_attrs, _, memoryview_slices) = scope.get_refcounted_entries()
cpp_class_attrs = [entry for entry in scope.var_entries
if entry.type.is_cpp_class]
if py_attrs or cpp_class_attrs or memoryview_slices or weakref_slot:
if py_attrs or cpp_class_attrs or memoryview_slices or weakref_slot or dict_slot:
self.generate_self_cast(scope, code)
if not is_final_type:
......@@ -1357,6 +1363,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if weakref_slot:
code.putln("if (p->__weakref__) PyObject_ClearWeakRefs(o);")
if dict_slot:
code.putln("if (p->__dict__) PyDict_Clear(p->__dict__);")
for entry in cpp_class_attrs:
code.putln("__Pyx_call_destructor(p->%s);" % entry.cname)
......@@ -1965,26 +1974,51 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln(
"};")
def generate_dict_getter(self, scope, code):
#if scope.name == 'QApplication':
# import ipdb;ipdb.set_trace()
func_name = scope.mangle_internal("__dict__getter")
dict_attr = scope.lookup_here("__dict__")
dict_name = dict_attr.cname
code.putln("")
code.putln("static PyObject *%s(PyObject *o, CYTHON_UNUSED void *x) {" % func_name)
self.generate_self_cast(scope, code)
code.putln("if (p->%s == 0){" % dict_name)
code.putln("p->%s = PyDict_New();" % dict_name)
code.putln("}")
code.putln("Py_INCREF(p->%s);" % dict_name)
code.putln("return p->%s;" % dict_name)
code.putln("}")
def generate_getset_table(self, env, code):
if env.property_entries:
dynamic_attributes = env.lookup_here("__dict__")
if env.property_entries or dynamic_attributes:
code.putln("")
code.putln(
"static struct PyGetSetDef %s[] = {" %
env.getset_table_cname)
if dynamic_attributes:
dict_getter_cname = env.mangle_internal("__dict__getter")
code.putln(
'{(char *)"__dict__", %s, 0, 0, 0},' % (
dict_getter_cname))
for entry in env.property_entries:
doc = entry.doc
if doc:
if doc.is_unicode:
doc = doc.as_utf8_string()
doc_code = doc.as_c_string_literal()
if entry.name == "__dict__":
continue
else:
doc_code = "0"
code.putln(
'{(char *)"%s", %s, %s, (char *)%s, 0},' % (
entry.name,
entry.getter_cname or "0",
entry.setter_cname or "0",
doc_code))
doc = entry.doc
if doc:
if doc.is_unicode:
doc = doc.as_utf8_string()
doc_code = doc.as_c_string_literal()
else:
doc_code = "0"
code.putln(
'{(char *)"%s", %s, %s, (char *)%s, 0},' % (
entry.name,
entry.getter_cname or "0",
entry.setter_cname or "0",
doc_code))
code.putln(
"{0, 0, 0, 0, 0}")
code.putln(
......@@ -2765,6 +2799,21 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
weakref_entry.cname))
else:
error(weakref_entry.pos, "__weakref__ slot must be of type 'object'")
dict_entry = scope.lookup_here("__dict__")
if dict_entry:
if dict_entry.type.cname == 'PyDict_Type':
tp_dictoffset = "%s.tp_dictoffset" % typeobj_cname
if type.typedef_flag:
objstruct = type.objstruct_cname
else:
objstruct = "struct %s" % type.objstruct_cname
code.putln("if (%s == 0) %s = offsetof(%s, %s);" % (
tp_dictoffset,
tp_dictoffset,
objstruct,
dict_entry.cname))
else:
error(dict_entry.pos, "__dict__ slot must be of type 'dict'")
def generate_exttype_vtable_init_code(self, entry, code):
# Generate code to initialise the C method table of an
......
......@@ -36,7 +36,7 @@ def c_safe_identifier(cname):
# There are some C limitations on struct entry names.
if ((cname[:2] == '__'
and not (cname.startswith(Naming.pyrex_prefix)
or cname == '__weakref__'))
or cname in ('__weakref__', '__dict__')))
or cname in iso_c99_keywords):
cname = Naming.pyrex_prefix + cname
return cname
......
......@@ -488,7 +488,7 @@ class GetSetSlot(SlotDescriptor):
# Slot descriptor for the table of attribute get & set methods.
def slot_code(self, scope):
if scope.property_entries:
if scope.property_entries or scope.lookup_here("__dict__"):
return scope.getset_table_cname
else:
return "0"
......
......@@ -35,7 +35,7 @@ Attributes
* Are stored directly in the object's C struct.
* Are fixed at compile time.
* You can't add attributes to an extension type instance at run time like in normal Python.
* You can't add attributes to an extension type instance at run time like in normal Python, unless you define a ``__dict__`` attribute.
* You can sub-class the extension type in Python to add attributes at run-time.
* There are two ways to access extension type attributes:
......@@ -410,6 +410,22 @@ Weak Referencing
cdef object __weakref__
==================
Dynamic Attributes
==================
* By default, you cannot dynamically add attributes to a ``cdef class`` instance at runtime.
* It can be enabled by declaring a C attribute of the ``dict`` type called ``__dict__``::
cdef class ExtendableAnimal:
"""This animal can be extended with new
attributes at runtime."""
cdef dict __dict__
.. note::
#. This can have a performance penalty, especially when using ``cpdef`` methods in a class.
=========================
External and Public Types
=========================
......
# mode: compile
cimport cython
cdef class Spam:
pass
cdef class SuperSpam(Spam):
cdef dict __dict__
cdef class SuperSpam2(SuperSpam):
pass
cdef class SuperSpam3(SuperSpam2):
pass
cdef public class UltraSpam [type UltraSpam_Type, object UltraSpam_Object]:
cdef dict __dict__
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment