Commit 47c742a4 authored by scoder's avatar scoder Committed by GitHub

Merge pull request #527 from empyrical/dynamic-attributes

Add special __dict__ attribute to extension types
parents 95d9d87b ffa59aa7
......@@ -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.defines_any(["__dict__"]):
self.generate_dict_getter_function(scope, code)
self.generate_property_accessors(scope, code)
self.generate_method_table(scope, code)
self.generate_getset_table(scope, code)
......@@ -1276,7 +1278,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
(entry.cname, entry.type.empty_declaration_code()))
for entry in py_attrs:
code.put_init_var_to_py_none(entry, "p->%s", nanny=False)
if entry.name == "__dict__":
code.putln("p->%s = PyDict_New(); Py_INCREF(p->%s);" % (entry.cname, entry.cname))
else:
code.put_init_var_to_py_none(entry, "p->%s", nanny=False)
for entry in memoryview_slices:
code.putln("p->%s.data = NULL;" % entry.cname)
......@@ -1323,11 +1328,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 +1366,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,6 +1977,20 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln(
"};")
def generate_dict_getter_function(self, scope, code):
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:
code.putln("")
......
......@@ -4615,10 +4615,15 @@ class CClassDefNode(ClassDefNode):
if has_body:
self.body.analyse_declarations(scope)
dict_entry = self.scope.lookup_here("__dict__")
if dict_entry and (not scope.defined and not scope.implemented):
dict_entry.getter_cname = self.scope.mangle_internal("__dict__getter")
self.scope.declare_property("__dict__", dict_entry.doc, dict_entry.pos)
if self.in_pxd:
scope.defined = 1
else:
scope.implemented = 1
env.allocate_vtable_names(self.entry)
for thunk in self.entry.type.defered_declarations:
......
......@@ -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
......
......@@ -509,6 +509,27 @@ class BaseClassSlot(SlotDescriptor):
base_type.typeptr_cname))
class DictOffsetSlot(SlotDescriptor):
# Slot descriptor for a class' dict offset, for dynamic attributes.
def slot_code(self, scope):
dict_entry = scope.lookup_here("__dict__")
if dict_entry:
if dict_entry.type.cname != 'PyDict_Type':
error(dict_entry.pos, "__dict__ slot must be of type 'dict'")
return "0"
type = scope.parent_type
if type.typedef_flag:
objstruct = type.objstruct_cname
else:
objstruct = "struct %s" % type.objstruct_cname
return ("offsetof(%s, %s)" % (
objstruct,
dict_entry.cname))
else:
return "0"
# The following dictionary maps __xxx__ method names to slot descriptors.
method_name_to_slot = {}
......@@ -814,7 +835,7 @@ slot_table = (
SyntheticSlot("tp_descr_get", ["__get__"], "0"),
SyntheticSlot("tp_descr_set", ["__set__", "__delete__"], "0"),
EmptySlot("tp_dictoffset"),
DictOffsetSlot("tp_dictoffset"),
MethodSlot(initproc, "tp_init", "__init__"),
EmptySlot("tp_alloc"), #FixedSlot("tp_alloc", "PyType_GenericAlloc"),
......
......@@ -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
=========================
......
cdef class MegaSpam:
cdef dict __dict__
# mode: run
cimport cython
cdef class Spam:
cdef dict __dict__
cdef class SuperSpam(Spam):
pass
cdef class MegaSpam:
pass
cdef public class UltraSpam [type UltraSpam_Type, object UltraSpam_Object]:
cdef dict __dict__
def test_class_attributes():
"""
>>> test_class_attributes()
'bar'
"""
o = Spam()
o.foo = "bar"
return o.foo
def test_subclass_attributes():
"""
>>> test_subclass_attributes()
'bar'
"""
o = SuperSpam()
o.foo = "bar"
return o.foo
def test_defined_class_attributes():
"""
>>> test_defined_class_attributes()
'bar'
"""
o = MegaSpam()
o.foo = "bar"
return o.foo
def test_public_class_attributes():
"""
>>> test_public_class_attributes()
'bar'
"""
o = UltraSpam()
o.foo = "bar"
return o.foo
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