diff --git a/changelog.d/407.change.rst b/changelog.d/407.change.rst new file mode 100644 index 00000000..8adc0602 --- /dev/null +++ b/changelog.d/407.change.rst @@ -0,0 +1 @@ +Fixed a reference leak where the original class would remain live after being replaced when ``slots=True`` is set. diff --git a/src/attr/_make.py b/src/attr/_make.py index 905f3092..a8d9c70c 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -493,7 +493,7 @@ class _ClassBuilder(object): cd = { k: v for k, v in iteritems(self._cls_dict) - if k not in tuple(self._attr_names) + ("__dict__",) + if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") } # We only add the names of attributes that aren't inherited. diff --git a/tests/test_make.py b/tests/test_make.py index e407b9f4..20d13b6a 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -5,6 +5,7 @@ Tests for `attr._make`. from __future__ import absolute_import, division, print_function import copy +import gc import inspect import itertools import sys @@ -1250,6 +1251,25 @@ class TestClassBuilder(object): assert C() == copy.deepcopy(C()) + def test_no_references_to_original(self): + """ + When subclassing a slots class, there are no stray references to the + original class. + """ + + @attr.s(slots=True) + class C(object): + pass + + @attr.s(slots=True) + class C2(C): + pass + + # The original C2 is in a reference cycle, so force a collect: + gc.collect() + + assert [C2] == C.__subclasses__() + class TestMakeCmp: """