1

Why does this work:

class Bunch(dict):

    def __init__(self, *args, **kwargs):
        super(Bunch, self).__init__(*args, **kwargs)
        self.__dict__ = self

b = Bunch()
b["a"] = 1
print(b.a)

Albeit with a circular reference:

import sys
print(sys.getrefcount(b))
# ref count is 3 when normally it is 2

But not this:

class Bunch(dict):

    @property
    def __dict__(self):
        return self

b = Bunch()
b["a"] = 1
b.a

# raises AttributeError

While __dict__ is an attribute by most outward appearances, there's some special behavior being implemented behind the scenes which is bypassing the descriptor logic created by the property.

rmorshea
  • 832
  • 1
  • 7
  • 25
  • Think about `self.__dict__ = self`. Obviously, that *can't* be equivalent to `self.__dict__['__dict__'] = self`, right? So `self.__dict__` must be magic. – Kevin Jul 25 '17 at 17:20
  • @OferSadan: No, function or not doesn't matter, and it's not quite a function in the second example anyway. – user2357112 Jul 25 '17 at 17:24

1 Answers1

2

The default __dict__ attribute already basically is a property. (It's not literally a property, but it's implemented through the descriptor protocol, just like property.)

When you set self.__dict__ = self, that goes through the __dict__ descriptor's __set__ method and replaces the dict used for instance attributes.

However, Python doesn't use the __dict__ descriptor to find the instance dict when performing attribute operations; it uses a different internal mechanism. Thus, creating your own __dict__ descriptor doesn't affect the attribute lookup mechanism for b.a in your second example.

user2357112
  • 260,549
  • 28
  • 431
  • 505