What is difference between self[name] and self.__dict__[name] in Python dictionary class?

312 Views Asked by At

I am not quite familiar with inheritance in Python, what is difference in following code between self.name and self.__dict__[name]? And if test = AttrDict(), why I can use dot to set attribute for test such like test.x = 100?

class AttrDict(dict):

    IMMUTABLE = '__immutable__'

    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__[AttrDict.IMMUTABLE] = False

    def __getattr__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]
        elif name in self:
            return self[name]
        else:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if not self.__dict__[AttrDict.IMMUTABLE]:
            if name in self.__dict__:
                self.__dict__[name] = value
            else:
                self[name] = value
        else:
            raise AttributeError(
                'Attempted to set "{}" to "{}", but AttrDict is immutable'.
                format(name, value)
            )
1

There are 1 best solutions below

0
matszwecja On

"why I can use dot to set attribute for test such like test.x = 100?"

You can always do that, that's how Python attributes works. If you are asking why you can set dict keys using that, that's because of __setattr__ method - it defines how Python handles setting attributes using .. With method signature _setattr__(self, name, value), when you do test.x = 100 you are effectively calling _setattr with parameters test, x, 100.

"what is difference in following code between self.name and self.__dict__[name]"

I'll just say, such a mix of interfaces is VERY error prone. You've got 3 different ways of setting something that might seems the same thing (self.name, self.__dict__[name] and self[name]), while in fact there are numerous situations where it's not the case.

Below example shows how weirdly this class behaves based on what method you use to set attribute:

test1 = AttrDict()
test1['clear'] = 'bar'
test1['foo'] = 'baz'
print(test1.clear)              # <built-in method clear of AttrDict object at 0x...>
#print(test1.__dict__['clear']) # KeyError
print(test1['clear'])           # bar
print(test1.foo)                # baz
#print(test1.__dict__['foo'])   # KeyError
print(test1['foo'])             # baz

test2 = AttrDict()
test2.clear = 'bar'
test2.foo = 'baz'
print(test2.clear)              # <built-in method clear of AttrDict object at 0x...>
#print(test2.__dict__['clear']) # KeyError
print(test2['clear'])           # bar
print(test2.foo)                # baz
#print(test2.__dict__['foo'])   # KeyError
print(test2['foo'])             # baz

test3 = AttrDict()
test3.__dict__['clear'] = 'bar'
test3.__dict__['foo'] = 'baz'
print(test3.clear)              # bar
print(test3.__dict__['clear'])  # bar
#print(test3['clear'])          # KeyError
print(test3.foo)                # baz
print(test3.__dict__['foo'])    # baz 
#print(test3['foo'])            # KeyError

As you can see, even most basic behaviour like below is broken with the way this class is written:

test2 = AttrDict()
test2.clear = 'bar'
print(test2.clear == 'bar') # False