Do not copy __weakref__ from original _cls_dict (#410)
self._cls_dict["__weakref__"] holds a reference to self._cls, preventing
self._cls from being released after the new, slots-enabled class is
returned.
Fixes #407
diff --git a/changelog.d/407.change.rst b/changelog.d/407.change.rst
new file mode 100644
index 0000000..8adc060
--- /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 905f309..a8d9c70 100644
--- a/src/attr/_make.py
+++ b/src/attr/_make.py
@@ -493,7 +493,7 @@
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 e407b9f..20d13b6 100644
--- a/tests/test_make.py
+++ b/tests/test_make.py
@@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function
import copy
+import gc
import inspect
import itertools
import sys
@@ -1250,6 +1251,25 @@
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:
"""