Bringing up this old post...

On 21 June 2010 15:41, Robert Bradshaw <rober...@math.washington.edu> wrote:
> On Jun 17, 2010, at 9:31 AM, Lisandro Dalcin wrote:
>
>> If we special case a __dict__ attribute in extension types, i.e:
>>
>> cdef class Foo:
>>    cdef dict __dict__
>>
>> and fill type->tp_dictoffset, then we can support __dict__ in
>> extension types.
>>
>> What do you think?
>
> Sounds like a good idea to me. Note that we check tp_dictoffset for
> fast dispatching for cpdef methods (which would be correct as a dict
> lookup *would* be needed if __dict__ is available).
>

I still have this patch lying around in my disk. I remember Stefan had
some objections. For example, when the user ask for __dict__, a new
dict is unconditionally created (in CPython, type dict are allocated
on-demand).  I propose to get this patch pushed now, and optimize
later (however, I really don't know how to safely implement this
optimization).


-- 
Lisandro Dalcin
---------------
CIMEC (INTEC/CONICET-UNL)
Predio CONICET-Santa Fe
Colectora RN 168 Km 472, Paraje El Pozo
3000 Santa Fe, Argentina
Tel: +54-342-4511594 (ext 1011)
Tel/Fax: +54-342-4511169
diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
index a66d43b..204d474 100644
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -24,6 +24,7 @@ import DebugFlags
 
 from Errors import error, warning
 from PyrexTypes import py_object_type
+from Builtin import dict_type as py_dict_type
 from Cython.Utils import open_new_file, replace_suffix
 from Code import UtilityCode
 from StringEncoding import EncodedString
@@ -1088,10 +1089,18 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
             code.putln("p->%s = %s%s;" % (
                 type.vtabslot_cname,
                 struct_type_cast, type.vtabptr_cname))
+        entry = scope.lookup_here("__dict__")
+        if entry and entry in py_attrs:
+            code.putln("p->%s = PyDict_New();" % entry.cname)
+            code.putln("if (p->%s == 0) { Py_DECREF(o); return 0;}" % entry.cname)
         for entry in py_attrs:
-            if scope.is_internal or entry.name == "__weakref__":
+            if scope.is_internal:
                 # internal classes do not need None inits
                 code.putln("p->%s = 0;" % entry.cname)
+            elif entry.name == "__dict__":
+                pass
+            elif entry.name == "__weakref__":
+                code.putln("p->%s = 0;" % entry.cname)
             else:
                 code.put_init_var_to_py_none(entry, "p->%s", nanny=False)
         entry = scope.lookup_here("__new__")
@@ -2162,9 +2171,14 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
                     code.putln(
                         'if (__Pyx_SetAttrString(%s, "%s", (PyObject *)&%s) < 0) %s' % (
                             Naming.module_cname,
-                            scope.class_name,
-                            typeobj_cname,
-                            code.error_goto(entry.pos)))
+                        scope.class_name,
+                        typeobj_cname,
+                        code.error_goto(entry.pos)))
+                dict_entry = scope.lookup_here("__dict__")
+                if dict_entry:
+                    if not (dict_entry.type is py_object_type or
+                            dict_entry.type is py_dict_type):
+                        error(dict_entry.pos, "__dict__ slot must be of type 'object' or 'dict'")
                 weakref_entry = scope.lookup_here("__weakref__")
                 if weakref_entry:
                     if weakref_entry.type is py_object_type:
diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py
index 0d0e576..afb54c2 100644
--- a/Cython/Compiler/Symtab.py
+++ b/Cython/Compiler/Symtab.py
@@ -37,7 +37,8 @@ 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 == '__weakref__'
+                  or cname == '__dict__'))
         or cname in iso_c99_keywords):
         cname = Naming.pyrex_prefix + cname
     return cname
diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py
index 4565c7e..aaba929 100644
--- a/Cython/Compiler/TypeSlots.py
+++ b/Cython/Compiler/TypeSlots.py
@@ -427,7 +427,24 @@ class BaseClassSlot(SlotDescriptor):
                 self.slot_name,
                 base_type.typeptr_cname))
 
+class DictOffsetSlot(SlotDescriptor):
+    #  Slot descriptor for the dict offset slot.
 
+    def __init__(self, name):
+        SlotDescriptor.__init__(self, name, dynamic = 1)
+
+    def generate_dynamic_init_code(self, scope, code):
+        entry = scope.lookup_here('__dict__')
+        if entry:
+            type =  scope.parent_type
+            objstruct = "struct %s" % type.objstruct_cname
+            code.putln("%s.%s = offsetof(%s, %s);" % (
+                type.typeobj_cname,
+                self.slot_name,
+                objstruct,
+                entry.cname))
+
+    
 # The following dictionary maps __xxx__ method names to slot descriptors.
 
 method_name_to_slot = {}
@@ -702,9 +719,9 @@ 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"),
     InternalMethodSlot("tp_new"),
diff --git a/tests/run/exttypedict.pyx b/tests/run/exttypedict.pyx
new file mode 100644
index 0000000..2bf1a13
--- /dev/null
+++ b/tests/run/exttypedict.pyx
@@ -0,0 +1,164 @@
+cdef class Foo:
+    u"""
+    >>> o = Foo()
+    >>> o.a = 1
+    >>> o.a
+    1
+    >>> del o.a
+    >>> o.a #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Foo' object has no attribute 'a'
+    >>> o.__dict__ #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Foo' object has no attribute '__dict__'
+    >>> d = o.get_dict()
+    >>> d
+    {}
+    >>> d['b'] = 7
+    >>> o.b
+    7
+    """
+    cdef dict __dict__ # private
+
+    def get_dict(self):
+        return (self.__dict__)
+
+cdef class Foo2(Foo):
+    u"""
+    >>> o = Foo2()
+    >>> o.a = 1
+    >>> o.a
+    1
+    >>> del o.a
+    >>> o.a #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Foo2' object has no attribute 'a'
+    >>> o.__dict__ #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Foo2' object has no attribute '__dict__'
+    """
+    pass
+
+
+cdef class Bar:
+    u"""
+    >>> o = Bar()
+    >>> o.__dict__
+    {}
+    >>> o.a = 1
+    >>> o.a
+    1
+    >>> o.__dict__
+    {'a': 1}
+    >>> o.__dict__['a'] = 2
+    >>> o.a
+    2
+    >>> del o.a
+    >>> o.__dict__
+    {}
+    >>> o.a #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Bar' object has no attribute 'a'
+    >>> d = {'b': 7}
+    >>> try: o.__dict__ = d
+    ... except (AttributeError, TypeError): print("ERROR")
+    ERROR
+    >>> o.__dict__.update(d)
+    >>> o.__dict__ == d
+    True
+    >>> o.b
+    7
+    """
+    cdef readonly dict __dict__ # public, readonly
+
+cdef class Bar2(Bar):
+    u"""
+    >>> o = Bar2()
+    >>> o.__dict__
+    {}
+    >>> o.a = 1
+    >>> o.a
+    1
+    >>> o.__dict__
+    {'a': 1}
+    >>> o.__dict__['a'] = 2
+    >>> o.a
+    2
+    >>> del o.a
+    >>> o.__dict__
+    {}
+    >>> o.a #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Bar2' object has no attribute 'a'
+    >>> d = {'b': 7}
+    >>> try: o.__dict__ = d
+    ... except (AttributeError, TypeError): print("ERROR")
+    ERROR
+    >>> o.__dict__.update(d)
+    >>> o.__dict__ == d
+    True
+    >>> o.b
+    7
+    """
+    pass
+
+# ---
+
+cdef class Spam:
+    u"""
+    >>> o = Spam()
+    >>> o.__dict__
+    {}
+    >>> o.a = 1
+    >>> o.a
+    1
+    >>> o.__dict__
+    {'a': 1}
+    >>> o.__dict__['a'] = 2
+    >>> o.a
+    2
+    >>> del o.a
+    >>> o.__dict__
+    {}
+    >>> o.a #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Spam' object has no attribute 'a'
+    >>> d = {'b': 7}
+    >>> o.__dict__ = d
+    >>> o.__dict__ is d
+    True
+    >>> o.b
+    7
+    """
+    cdef public dict __dict__ # public, read/write
+    cdef x, y, z
+
+cdef class Spam2(Spam):
+    u"""
+    >>> o = Spam2()
+    >>> o.__dict__
+    {}
+    >>> o.a = 1
+    >>> o.a
+    1
+    >>> o.__dict__
+    {'a': 1}
+    >>> o.__dict__['a'] = 2
+    >>> o.a
+    2
+    >>> del o.a
+    >>> o.__dict__
+    {}
+    >>> o.a #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'exttypedict.Spam2' object has no attribute 'a'
+    """
+    pass
_______________________________________________
cython-devel mailing list
cython-devel@python.org
http://mail.python.org/mailman/listinfo/cython-devel

Reply via email to