Skip to content
Advertisement

When is `__dict__` re-initialized?

I subclass dict so that the attributes are identical to the keys:

class DictWithAttr(dict):
    def __init__(self, *args, **kwargs):
        self.__dict__ = self
        super(DictWithAttr, self).__init__(*args, **kwargs)
        print(id(self), id(self.__dict__))

    def copy(self):
        return DictWithAttr(self.__dict__)

    def __repr__(self):
        return repr({k:v for k, v in self.items() if k != '__dict__'})

and it works as expected:

d = DictWithAttr(x=1, y=2)    # 139917201238328 139917201238328
d.y = 3
d.z = 4
d['w'] = 5
print(d)                      # {'x': 1, 'y': 3, 'z': 4, 'w': 5}
print(d.__dict__)             # {'x': 1, 'y': 3, 'z': 4, 'w': 5}
print(d.z, d.w)               # 4 5

But if I re-write __setattr__ as

    ...
    def __setattr__(self, key, value):
        self[key] = value
    ...

then __dict__ will be re-created in initialization and the attributes will turn inaccessible:

d = DictWithAttr(x=1, y=2)    # 140107290540344 140107290536264
d.y = 3
d.z = 4
d['w'] = 5
print(d)                      # {'x': 1, 'y': 3, 'z': 4, 'w': 5}
print(d.__dict__)             # {}
print(d.z, d.w)               # AttributeError: 'DictWithAttr' object has no attribute 'z'

Adding a paired __getattr__ as below will get around the AttributeError

    ...
    def __getattr__(self, key):
        return self[key]
    ...

but still __dict__ is cleared:

d = DictWithAttr(x=1, y=2)    # 139776897374520 139776897370944
d.y = 3
d.z = 4
d['w'] = 5
print(d)                      # {'x': 1, 'y': 3, 'z': 4, 'w': 5}
print(d.__dict__)             # {}
print(d.z, d.w)               # 4 5

Thanks for any explanations.

Advertisement

Answer

There’s no reinitialization. Your problem is that self.__dict__ = self hits your __setattr__ override. It’s not actually changing the dict used for attribute lookups. It’s setting an entry for the '__dict__' key on self and leaving the attribute dict untouched.

If you wanted to keep your (pointless) __setattr__ override, you could bypass it in __init__:

object.__setattr__(self, '__dict__', self)

but it’d be easier to just take out your __setattr__ override. While you’re at it, take out that __repr__, too – once you fix your code, the only reason there would be a '__dict__' key is if a user sets it themselves, and if they do that, you should show it.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement