Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1637.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Preserve slots declared directly on a class decorated with `@attrs.define` or `@attr.s`.
18 changes: 16 additions & 2 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,10 +860,22 @@ def _create_slots_class(self):
"""
Build and return a new class with a `__slots__` attribute.
"""
own_slots = self._cls.__dict__.get("__slots__", ())
if isinstance(own_slots, str):
own_slots = (own_slots,)
else:
own_slots = tuple(own_slots)

cd = {
k: v
for k, v in self._cls_dict.items()
if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
if k
not in (
*tuple(self._attr_names),
*own_slots,
"__dict__",
"__weakref__",
)
}

# 3.14.0rc2+
Expand Down Expand Up @@ -903,7 +915,9 @@ def _create_slots_class(self):

base_names = set(self._base_names)

names = self._attr_names
names = self._attr_names + tuple(
name for name in own_slots if name not in self._attr_names
)
if (
self._weakref_slot
and "__weakref__" not in getattr(self._cls, "__slots__", ())
Expand Down
51 changes: 51 additions & 0 deletions tests/test_slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,57 @@ def test_slots_being_used():
assert attr.asdict(slot_instance) == attr.asdict(non_slot_instance)


def test_explicit_slots_are_preserved():
"""Slots declared on the class are retained when attrs creates the class."""

@attrs.define
class ExplicitSlots:
__slots__ = ("private",)

public: str = "foo"

instance = ExplicitSlots()
instance.private = 1

assert instance.private == 1
assert instance.public == "foo"
assert {"__weakref__", "private", "public"} == set(ExplicitSlots.__slots__)


def test_explicit_string_slot_is_preserved():
"""A single string slot declaration survives attrs class rebuilding."""

@attrs.define
class ExplicitStringSlot:
__slots__ = "private" # noqa: PLC0205 - exercise the supported string form

public: str = "foo"

instance = ExplicitStringSlot()
instance.private = 1

assert instance.private == 1
assert instance.public == "foo"
assert {"__weakref__", "private", "public"} == set(
ExplicitStringSlot.__slots__
)


def test_explicit_slot_for_attribute_is_preserved():
"""A declared slot matching an attrs field is not duplicated."""

@attrs.define
class ExplicitAttributeSlot:
__slots__ = ("public",)

public: str

instance = ExplicitAttributeSlot("foo")

assert instance.public == "foo"
assert {"__weakref__", "public"} == set(ExplicitAttributeSlot.__slots__)


def test_slots_base_of_slotted():
"""
The (hopefully gc'ed) temporary base class of a slotted class contains a
Expand Down
Loading