slots=True and keyword-only fields

dataclasses · Advanced · 11 min

Large numbers of instances and many-field constructors are where these options pay off.

`slots=True` (3.10+) generates a class with __slots__ for its fields. Instances then have no per-instance __dict__:

  • noticeably less memory per instance, which matters for millions of objects,
  • slightly faster attribute access,
  • assigning an attribute that isn't a field raises AttributeError, which catches typos.

Because __slots__ must exist at class creation, the decorator builds a new class. Consequences: anything that captured the original class before the swap (for example a registry filled by a decorator applied underneath) points at the old class, and weak references need weakref_slot=True (3.11).

Keyword-only fields make call sites self-documenting and remove the default-ordering restriction:

  • @dataclass(kw_only=True) makes every field keyword-only.
  • field(kw_only=True) marks individual fields.
  • A pseudo-field _: KW_ONLY makes all fields after it keyword-only.

Keyword-only fields are moved after positional ones in __init__, so a required keyword-only field may follow a field with a default. That's the clean fix for inheritance ordering problems (next lesson).

Config(host="x", port=1, debug=True) reads far better than Config("x", 1, True); use keyword-only for anything beyond two or three fields.

Example

import sys
from dataclasses import dataclass, KW_ONLY
@dataclass
class PlainPoint:
    x: float; y: float

@dataclass(slots=True)
class SlotPoint:
    x: float; y: float

p, s = PlainPoint(1, 2), SlotPoint(1, 2)
print(hasattr(p, "__dict__"), hasattr(s, "__dict__"))   # True False
print(sys.getsizeof(p) + sys.getsizeof(p.__dict__), sys.getsizeof(s))  # e.g. 344 48
try:
    s.z = 3
except AttributeError as e:
    print("typo caught:", e)        # 'SlotPoint' object has no attribute 'z' ...

@dataclass
class Request:
    url: str
    _: KW_ONLY
    timeout: float = 10.0
    retries: int                    # required, allowed after a default because kw-only
print(Request("https://x.io", retries=2))   # Request(url='https://x.io', timeout=10.0, retries=2)

Remember

  • slots=True removes __dict__: less memory, faster access, typo protection
  • slots=True returns a new class; use weakref_slot=True for weak references
  • kw_only=True, field(kw_only=True) or _: KW_ONLY make fields keyword-only

Watch out for

  • Adding attributes dynamically to slotted instances, which raises AttributeError.

Check yourself

  1. Why can a required keyword-only field follow a field with a default?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/dataclasses.html#dataclasses.KW_ONLY