'*Why* does object() not support `setattr`, but derived classes do?

Today I stumbled upon the following behaviour:

class myobject(object):
    """Should behave the same as object, right?"""

obj = myobject()
obj.a = 2        # <- works
obj = object()
obj.a = 2        # AttributeError: 'object' object has no attribute 'a'

I want to know what is the logic behind designing the language to behave this way, because it feels utterly paradoxical to me. It breaks my intuition that if I create a subclass, without modification, it should behave the same as the parent class.


EDIT: A lot of the answers suggest that this is because we want to be able to write classes that work with __slots__ instead of __dict__ for performance reasons. However, we can do:

class myobject_with_slots(myobject):
    __slots__ = ("x",)
    
obj = myobject_with_slots()
obj.x = 2
obj.a = 2
assert "a" in obj.__dict__      # ✔
assert "x" not in obj.__dict__  # ✔

So it seems we can have both __slots__ and __dict__ at the same time, so why doesn't object allow both, but one-to-one subclasses do?



Solution 1:[1]

Consider this code:

class A:
    __slots__ = ()

class B(A):
    __slots__ = ("x", "y")

b = B()
b.z = 1  # AttributeError

__slots__ = ("x", "y") means that B instances don't have a __dict__ and can only have attributes x and y. This is good for performance.

If you remove the __slots__ from A, then A instances get a __dict__, which means so do their subclasses, particularly B. This reduces the effectiveness of __slots__ at improving performance.

Because object is a superclass of all classes, you can think of it like A here, having __slots__ = () and no __dict__ so that other classes can also avoid having a __dict__ and fully benefit from custom __slots__.

Solution 2:[2]

Short answer

object() by default does not have an attribute dictionary (__dict__). It allows the object() class and anything that inherits from it to save a few bytes.

Why is it so important?

Every class in Python inherits from object(). Classes like str, dict, tuple and int are used endlessly both internally and externally.

Having an instance dictionary means that every object in Python will be both larger (consume more memory) and slower (every attribute will cause a dictionary lookup).

In order to improve flexibility, by default, user-created classes do come with an instance __dict__. It allows us to patch instances, hook on methods, dynamically inject dependencies and offers an endless amount of different benefits. It is what gives Python its strength as a dynamic programming language and one of the paramount reasons for its success.

To prevent creating one, you may set __slots__ like so:

class A:
    __slots__ = ()

A().abc = 123  # Will throw an error

Having no instance dictionary means that regular attribute access can skip searching __dict__. The faster attribute access leads to a large overall improvement in the Python runtime, but will reduce flexibility in your class usage.

The way attribute lookup works without using __dict__ is out of scope of the question. You may read more about __slots__ in the documentation.


For your second question:

Any user-made class that doesn't have __slots__ has an instance dictionary (__dict__).

If you subclass it, you can't add __slots__ and remove the dictionary of the parent class, it already exists.

Having both __slots__ and a dictionary removes most of the upsides of using __slots__ which is saving space and preventing a dictionary creation.

>>> import sys
>>> class A:
...  pass
...
>>> class B:
...  __slots__ = ()
...
>>> sys.getsizeof(A())
48
>>> sys.getsizeof(B())
32
>>> class C(A):
...  __slots__ = ()
...
>>> sys.getsizeof(C())
48
>>> C.__dict__
mappingproxy({'__module__': '__main__', '__slots__': (), '__doc__': None})

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2