inconsistency when swapping obj.__dict__ with a dict-like object...
Hi! here is a simple piece of code <pre> ---cut--- class Dict(dict): def __init__(self, dct={}): self._dict = dct def __getitem__(self, name): return self._dct[name] def __setitem__(self, name, value): self._dct[name] = value def __delitem__(self, name): del self._dct[name] def __contains__(self, name): return name in self._dct def __iter__(self): return iter(self._dct) class A(object): def __new__(cls, *p, **n): o = object.__new__(cls) o.__dict__ = Dict() return o a = A() a.xxx = 123 print a.__dict__._dict a.__dict__._dict['yyy'] = 321 print a.yyy --uncut-- </pre> Here there are two problems, the first is minor, and it is that anything assigned to the __dict__ attribute is checked to be a descendant of the dict class (mixing this in does not seem to work)... and the second problem is a real annoyance, it is that the mapping protocol supported by the Dict object in the example above is not used by the attribute access mechanics (the same thing that once happened in exec)... 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.... thanks. -- Alex.
Alex A. Naanou wrote:
Hi!
here is a simple piece of code <pre> ---cut--- class Dict(dict): def __init__(self, dct={}): self._dict = dct def __getitem__(self, name): return self._dct[name] def __setitem__(self, name, value): self._dct[name] = value def __delitem__(self, name): del self._dct[name] def __contains__(self, name): return name in self._dct def __iter__(self): return iter(self._dct)
class A(object): def __new__(cls, *p, **n): o = object.__new__(cls) o.__dict__ = Dict() return o
a = A() a.xxx = 123 print a.__dict__._dict a.__dict__._dict['yyy'] = 321 print a.yyy
--uncut-- </pre>
Here there are two problems, the first is minor, and it is that anything assigned to the __dict__ attribute is checked to be a descendant of the dict class (mixing this in does not seem to work)... and the second problem is a real annoyance, it is that the mapping protocol supported by the Dict object in the example above is not used by the attribute access mechanics (the same thing that once happened in exec)...
Actually, overriding __getattribute__() does work; __getattr__() and __getitem__() doesn't. This was brought up last month at some point without any resolve (I think Steve Bethard pointed it out).
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....
Semantically necessary, no. But simplicity- and performance-wise, maybe. If you grep around in Objects/classobject.c, for instance, you will see PyClassObject.cl_dict is accessed using PyDict_GetItem() and I spotted at least one use of PyDict_DelItem(). To use the mapping protocol would require changing all of these to PyObject_GetItem() and such. Which will be a performance penalty compared to PyDict_GetItem(). So the question is whether the flexibility is worth it. -Brett
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@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net
On Apr 5, 2005 8:46 PM, Brett C.
Alex A. Naanou wrote:
Here there are two problems, the first is minor, and it is that anything assigned to the __dict__ attribute is checked to be a descendant of the dict class (mixing this in does not seem to work)... and the second problem is a real annoyance, it is that the mapping protocol supported by the Dict object in the example above is not used by the attribute access mechanics (the same thing that once happened in exec)...
Actually, overriding __getattribute__() does work; __getattr__() and __getitem__() doesn't. This was brought up last month at some point without any resolve (I think Steve Bethard pointed it out).
Yeah, here's the link: http://mail.python.org/pipermail/python-dev/2005-March/051837.html I've pointed out three possible "solutions" there, but they all have some significant drawbacks. I took the complete silence on the topic as an indication that none of the options were acceptable. STeVe -- You can wordify anything if you just verb it. --- Bucky Katt, Get Fuzzy
participants (4)
-
Alex A. Naanou
-
Brett C.
-
Nick Coghlan
-
Steven Bethard