[Python-Dev] inconsistency when swapping obj.__dict__ with a dict-like object...

Nick Coghlan ncoghlan at gmail.com
Wed Apr 6 13:31:44 CEST 2005

> P.S. (IMHO) the type check here is not that necessary (at least in its
> current state), as what we need to assert is not the relation to the
> dict class but the support of the mapping protocol....

The type-check is basically correct - as you have discovered, type & object use 
the PyDict_* API internally (for speed reasons, as I understand it), so 
supporting the mapping API is not really sufficient for something assigned to 
__dict__. Changing this for exec is one thing, as speed of access to the locals 
dict isn't likely to have a major impact on the overall performance of such 
code, but I would expect changing class dictionary access code in a similar way 
would have a major (detrimental) performance impact.

Depending on the use case, it is possible to work around the problem by defining 
__dict__, __getattribute__, __setattr__ and __delattr__ in the class. defining 
__dict__ sidesteps the type error, defining the other three methods then let's 
you get around the fact that the standard C-level dict pointer is no longer 
being updated, as well as making sure the general mapping API is used, rather 
than the concrete PyDict_* API. This is kinda ugly, but it works as long as any 
C code using the class __dict__ goes via the attribute access machinery and 
doesn't try to get the dictionary automatically supplied by Python by digging 
directly into the type structure.

from UserDict import DictMixin
class Dict(DictMixin):
     def __init__(self, dct=None):
         if dct is None:
             dct = {}
         self._dict = dct
     def __getitem__(self, name):
         return self._dict[name]
     def __setitem__(self, name, value):
         self._dict[name] = value
     def __delitem__(self, name):
         del self._dict[name]
     def keys(self):
         return self._dict.keys()

class A(object):
     def __new__(cls, *p, **n):
         o = object.__new__(cls)
         super(A, o).__setattr__('__dict__', Dict())
         return o
     __dict__ = None
     def __getattr__(self, attr):
             return self.__dict__[attr]
         except KeyError:
             raise AttributeError("%s" % attr)
     def __setattr__(self, attr, value):
         if attr in self.__dict__ or not hasattr(self, attr):
             self.__dict__[attr] = value
             super(A, self).__setattr__(attr, value)
     def __delattr__(self, attr):
         if attr in self.__dict__:
             del self.__dict__[attr]
             super(A, self).__delattr__(attr)

Py> a = A()
Py> a.__dict__._dict
Py> a.xxx = 123
Py> a.__dict__._dict
{'xxx': 123}
Py> a.__dict__._dict['yyy'] = 321
Py> a.yyy
Py> a.__dict__._dict
{'xxx': 123, 'yyy': 321}
Py> del a.xxx
Py> a.__dict__._dict
{'yyy': 321}
Py> del a.xxx
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 21, in __delattr__
AttributeError: xxx
Py> a.__dict__ = {}
Py> a.yyy
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 11, in __getattr__
AttributeError: yyy


Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia

More information about the Python-Dev mailing list