8

Say I have a class which defines __slots__:

class Foo(object):
    __slots__ = ['x']

    def __init__(self, x=1):
        self.x = x

    # will the following work?
    def __setattr__(self, key, value):
        if key == 'x':
            object.__setattr__(self, name, -value) # Haha - let's set to minus x

Can I define __setattr__() for it?

Since Foo has no __dict__, what will it update?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
bavaza
  • 10,319
  • 10
  • 64
  • 103

1 Answers1

14

All your code does, apart from negate the value, is call the parent class __setattr__, which is exactly what would happen without your __setattr__ method. So the short answer is: Sure you can define a __setattr__.

What you cannot do is redefine __setattr__ to use self.__dict__, because instances of a class with slots do not have a __dict__ attribute. But such instances do have a self.x attribute, it's contents are just not stored in a dictionary on the instance.

Instead, slot values are stored in the same location a __dict__ instance dictionary would otherwise be stored; on the object heap. Space is reserved for len(__slots__) references, and descriptors on the class access these references on your behalf.

So, in a __setattr__ hook, you can just call those descriptors directly instead:

def __setattr__(self, key, value):
    if key == 'x':
        Foo.__dict__[key].__set__(self, -value)

Interesting detour: yes, on classes without a __slots__ attribute, there is a descriptor that would give you access to the __dict__ object of instances:

>>> class Bar(object): pass
... 
>>> Bar.__dict__['__dict__']
<attribute '__dict__' of 'Bar' objects>
>>> Bar.__dict__['__dict__'].__get__(Bar(), Bar)
{}

which is how normal instances can look up self.__dict__. Which makes you wonder where the Bar.__dict__ object is found. In Python, it is turtles all the way down, you'd look that object up on the type object of course:

>>> type.__dict__['__dict__']
<attribute '__dict__' of 'type' objects>
>>> type.__dict__['__dict__'].__get__(Bar, type)
dict_proxy({'__dict__': <attribute '__dict__' of 'Bar' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None})
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • does __dict__ work here? is that because the effective address of __dict__ and the heap items is the same, and this is kind of an easter egg? or did you mean __slots__? – Corley Brigman Oct 24 '13 at 13:22
  • @CorleyBrigman: do not confuse the `__dict__` of the **class** with that of the **instance**. Instances of the class `Foo` do not have a `__dict__` attribute. – Martijn Pieters Oct 24 '13 at 13:23
  • @CorleyBrigman: And `Foo` instances have no `__dict__` attribute, because `Foo.__dict__['__dict__']` raises a `KeyError`. – Martijn Pieters Oct 24 '13 at 13:24
  • @MartijnPieters - not sure I got this. Does `object` have a `__dict__` even though `Foo` does not? I'm guessing not, in which case, what does calling `object.__setattr__()` change? – bavaza Oct 24 '13 at 13:35
  • Ahh... i see, i missed that you were accessing it through the class (`Foo.__dict__`) instead of `self.__dict__`. that makes perfect sense. – Corley Brigman Oct 24 '13 at 13:40
  • 1
    @bavaza: `object.__dict__` does exist, but that's not what an instance would use. `object.__setattr__` would look for data descriptors before it would look for `instance.__dict__`, and `Foo.__dict__['x']` *is* a data descriptor (it has both `__get__` and `__set__` hooks), so `instance.__dict__` is never looked up by `object.__setattr__`. – Martijn Pieters Oct 24 '13 at 13:43
  • @MartijnPieters [Turtles all the way down...](https://en.wikipedia.org/wiki/Turtles_all_the_way_down) until you reach Logo Funny enough, yesterday I read [these other historical outlines](http://boveyking.blogspot.com/2006/01/?m=1). I think they deserve a merge. Thanks for the ref. – Nuno André Apr 10 '21 at 02:54