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
10 changes: 10 additions & 0 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,17 @@ def _create_slots_class(self):
# We only add the names of attributes that aren't inherited.
# Setting __slots__ to inherited attributes wastes memory.
slot_names = [name for name in names if name not in base_names]
original_slots = self._cls_dict.get("__slots__", ())

if isinstance(original_slots, str):
original_slots = (original_slots,)

for slot in original_slots:
if slot not in slot_names and slot not in base_names:
slot_names.append(slot)

for slot in original_slots:
cd.pop(slot, None)
# There are slots for attributes from current class
# that are defined in parent classes.
# As their descriptors may be overridden by a child class,
Expand Down
116 changes: 116 additions & 0 deletions tests/test_slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,122 @@ class C2(C1):
assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)


def test_slots_declared_on_attrs_class_are_preserved():
"""User-defined slots are preserved on attrs classes."""

@attrs.define
class A:
__slots__ = ("another_private", "private")
public: str = "foo"

a = A()
a.private = 1
a.another_private = 2

assert "private" in A.__slots__
assert "another_private" in A.__slots__
assert a.private == 1
assert a.another_private == 2


def test_string_slot_declared_on_attrs_class_is_preserved():
"""String-based user-defined slots are preserved."""

@attrs.define
class A:
__slots__ = "private" # noqa: PLC0205
public: str = "foo"

a = A()
a.private = 1

assert "private" in A.__slots__
assert a.private == 1


def test_user_defined_weakref_slot_is_preserved():
"""A user-defined __weakref__ slot is preserved."""

@attrs.define
class A:
__slots__ = ("__weakref__", "private")
public: str = "foo"

a = A()
a.private = 1

assert "__weakref__" in A.__slots__
assert "private" in A.__slots__
assert a.private == 1


def test_user_defined_slots_are_preserved_with_inherited_slots():
"""User-defined slots are preserved alongside inherited slots."""

class Parent:
__slots__ = ("parent_slot",)

@attrs.define
class Child(Parent):
__slots__ = ("another_private", "private")
public: str = "foo"

child = Child()
child.parent_slot = 1
child.private = 2
child.another_private = 3

assert "private" in Child.__slots__
assert "another_private" in Child.__slots__
assert "parent_slot" not in Child.__slots__
assert child.private == 2
assert child.another_private == 3
assert child.parent_slot == 1


def test_user_defined_slots_already_exist_in_attrs_base_class():
"""User-defined slots do not duplicate slots inherited from attrs."""

@attrs.define
class Parent:
parent_slot: int = 0

@attrs.define
class Child(Parent):
__slots__ = ("parent_slot", "private")
public: str = "foo"

child = Child()
child.parent_slot = 1
child.private = 2

assert "parent_slot" not in Child.__slots__
assert "private" in Child.__slots__
assert child.parent_slot == 1
assert child.private == 2


def test_user_defined_slots_already_exist_in_base_class():
"""User-defined slots already present in a base are not duplicated."""

class Parent:
__slots__ = ("parent_slot",)

@attrs.define
class Child(Parent):
__slots__ = ("parent_slot", "private")
public: str = "foo"

child = Child()
child.parent_slot = 1
child.private = 2

assert "parent_slot" not in Child.__slots__
assert "private" in Child.__slots__
assert child.parent_slot == 1
assert child.private == 2


def test_inheritance_from_slots_with_attribute_override():
"""
Inheriting from a slotted class doesn't re-create existing slots
Expand Down
Loading