What does defining `__slots__` on a Python class change about its instances?
answer
- Instances stop carrying a dictionary
- Only the declared names are assignable
- A typo raises instead of creating
- Declared is not the same as assigned
- Class variable of the same name conflicts
basics
~10 sInstances lose their per-instance __dict__. Only the names listed in __slots__ can be assigned, each into a fixed storage slot, and assigning any other attribute raises AttributeError instead of silently creating it.
solid answer
~50 s`__slots__` is a class-body assignment, usually a tuple of attribute names, that the class machinery reads while building the type. Instead of giving each instance a `__dict__` mapping, the type reserves one fixed storage slot per listed name and exposes each one as a class-level descriptor. The practical consequences are: `obj.__dict__` and `vars(obj)` raise, only the listed names are assignable, a typo like `p.corrdinate = 1` raises AttributeError instead of quietly creating a new attribute, and reading a slot that was never assigned also raises AttributeError. Class attributes, methods and properties are unaffected, because they live on the class, not in the instance. One gotcha shows up immediately: a name that appears both in `__slots__` and as a class variable in the same body raises ValueError at class-creation time, since the descriptor and the class variable would collide.
code
python · 14 linesclass Point:
__slots__ = ("x", "y")
p = Point()
p.x = 1
p.y = 2
try:
p.z = 3
except AttributeError as exc:
print("rejected:", exc)
try:
p.__dict__
except AttributeError:
print("no instance __dict__")go deeper
Be ready to state the two facts plainly: no per-instance __dict__, and only the listed names may be assigned. Show the AttributeError on a typo — that is the demonstration interviewers want to see.
Explain the mechanics behind the behaviour: the names become class-level descriptors over fixed instance storage, a declared slot starts out unset, and a same-named class variable is a class-creation error.
Show the operational consequences: code that introspects __dict__ breaks, del puts a slot back into the unset state, and the typo-safety only holds if every class down the hierarchy declares slots too.
Own the policy question — where declaring slots is worth the rigidity across a codebase, when the "__dict__" escape hatch signals the constraint was wrong, and how you keep the convention from decaying as the hierarchy grows.
## What the declaration actually is `__slots__` is not a keyword and not a decorator. It is an ordinary assignment in a class body whose name the type-creation machinery treats specially: ```python class Point: __slots__ = ("x", "y") ``` When the class object is created, the machinery sees the `__slots__` entry in the class namespace, reserves one fixed storage cell per listed name inside the instance's memory layout, and installs a class-level descriptor per name. It then *omits* the pointer that a normal class would carry for a per-instance `__dict__`. The value may be any iterable of strings. A bare string is special-cased as a single name, so `__slots__ = "only"` declares one slot called `only`, not four slots named after its characters. A tuple is the conventional form precisely because it reads as a fixed, closed list. ## The four visible consequences **1. There is no instance dictionary.** `p.__dict__` raises AttributeError, and `vars(p)` raises TypeError, because `vars()` requires the object to have a `__dict__`. Any code that introspects instances by reading `__dict__` — serializers, debug dumps, hand-rolled copiers — stops working against slotted instances and must go through the declared names instead. **2. Only the declared names are assignable.** Assigning anything else raises AttributeError. This is the semantic change that matters most in day-to-day work: on a normal class, `p.corrdinate = 1` is a silent typo that creates a brand-new attribute and produces a bug far from its cause. On a slotted class the interpreter refuses the assignment at the point of the mistake. The message names both facts: the object has no such attribute *and* no `__dict__` in which to create one. **3. A declared but unassigned slot raises on read.** Declaring a name reserves storage; it does not give it a value. Reading `p.y` before anything assigned it raises AttributeError just as an unknown attribute would. `del p.y` puts the slot back into that unset state, and reading it afterwards raises again. So "the attribute exists" and "the attribute is declared" are different statements — the standard test is still `hasattr` or a `try`/`except AttributeError`, not membership in the `__slots__` tuple. **4. Class-level machinery is untouched.** Methods, class variables, `property` objects, `classmethod` and `staticmethod` all live on the class, so they behave normally. What is forbidden is a *collision*: a name that is both listed in `__slots__` and assigned in the same class body raises `ValueError` while the class is being created, because the slot descriptor and the class variable would occupy the same class-namespace key. ```python class Bad: __slots__ = ("x",) x = 0 # ValueError: 'x' in __slots__ conflicts with class variable ``` ## What `__slots__` is not It is not access control. Slots do not make attributes private, read-only or typed — a declared slot accepts any object, and the declaration is plainly visible on the class. It is not validation either: nothing checks the value you store. It is also not a guarantee that inherits. `__slots__` constrains only the class that declares it. A subclass that does not declare its own `__slots__` gets a `__dict__` back, and every instance of that subclass accepts arbitrary attributes again — so a codebase that relies on slots for typo-safety has to declare them all the way down the hierarchy. Finally, the escape hatch is explicit: listing `"__dict__"` inside `__slots__` gives instances a dictionary *in addition to* the slots, which restores arbitrary attribute assignment while keeping the declared names in fixed storage. That is occasionally what you want; more often, seeing it in a diff means someone hit an AttributeError and silenced the mechanism rather than fixing the caller. ## Version note The behaviour described here is stable and unchanged through Python 3.14. The one adjacent change worth knowing is that since 3.11 `object.__getstate__` is a real method that already understands slots, so a slotted class pickles and copies correctly without a hand-written state hook — a step that older code often carried. ## Checking it at runtime `Point.__slots__` reports only what *that* class body declared — it is a plain class attribute, not a computed summary of the whole hierarchy — so a subclass's `__slots__` does not list the names it inherited from its base. To ask what an instance can actually hold you look at the descriptors the type exposes rather than at the tuple: each declared name appears on the class as a descriptor object whose type is named `member_descriptor`, and `dir(obj)` includes inherited slot names alongside methods. That distinction matters when you write a helper that dumps an object's state: reading the declaring class's `__slots__` tuple quietly omits every inherited field, which is the usual reason a hand-rolled serializer loses half its data the day someone adds a subclass.
- If a name is listed in `__slots__` but never assigned, what does reading it give you?An AttributeError. Declaring a name reserves storage but stores no value, so the slot starts out unset and reads raise exactly as an unknown attribute would. `del obj.name` returns a slot to that unset state, and the next read raises again. Test with `hasattr` or `try`/`except AttributeError`; membership in the `__slots__` tuple only tells you the attribute is permitted, not that it exists.
- Does `__slots__ = 'value'`, written as a plain string rather than a tuple, work?Yes, and it means one slot named `value` — a bare string is special-cased as a single name rather than iterated character by character. It works, but the tuple form is conventional because it reads as a closed list and does not invite the reader to wonder whether the string was iterated. Any iterable of strings is accepted.
- Can you keep a `__dict__` while still declaring slots?Yes: list `"__dict__"` inside `__slots__` and instances get both the fixed slots and a dictionary, so arbitrary attributes are assignable again. It is a deliberate escape hatch, occasionally useful when a mixin or a framework needs to stash attributes, but adding it to silence an AttributeError throws away the typo-catching that motivated the declaration in the first place.
A normal instance is a notebook you can write any new heading into; a slotted instance is a pre-printed form with a fixed set of boxes, and there is no margin to write in.
saying these in an interview costs you the question
- Says __slots__ only affects performance and changes no semantics
- Claims you can still assign any attribute after declaring slots
- Thinks vars(obj) or obj.__dict__ still works on a slotted instance
- Believes __slots__ makes attributes private or read-only
- Assumes a listed slot already holds a default value
- Thinks methods and properties stop working under slots