[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):
         try:
             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
         else:
             super(A, self).__setattr__(attr, value)
     def __delattr__(self, attr):
         if attr in self.__dict__:
             del self.__dict__[attr]
         else:
             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
321
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

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net


More information about the Python-Dev mailing list