Skip to content

Commit e9b6872

Browse files
gh-74876: Fix copying and pickling weakref.WeakSet
1 parent dc5e02b commit e9b6872

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed

Lib/_weakrefset.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def __exit__(self, e, t, b):
3434

3535

3636
class WeakSet:
37+
__slots__ = ('__dict__', '__weakref__', 'data',
38+
'_remove', '_pending_removals', '_iterating')
3739
def __init__(self, data=None):
3840
self.data = set()
3941
def _remove(item, selfref=ref(self)):
@@ -80,7 +82,11 @@ def __contains__(self, item):
8082
return wr in self.data
8183

8284
def __reduce__(self):
83-
return self.__class__, (list(self),), self.__getstate__()
85+
dict, slots = self.__getstate__()
86+
for name in WeakSet.__slots__:
87+
slots.pop(name, None)
88+
state = (dict, slots) if dict or slots else None
89+
return self.__class__, (list(self),), state
8490

8591
def add(self, item):
8692
if self._pending_removals:

Lib/test/test_weakset.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
from weakref import WeakSet
33
import copy
4+
import pickle
45
import string
56
from collections import UserString as ustr
67
from collections.abc import Set, MutableSet
@@ -463,12 +464,16 @@ def test_copying(self):
463464
dup = copy.copy(s)
464465
self.assertIsInstance(dup, cls)
465466
self.assertEqual(dup, s)
467+
self.assertEqual(sorted(dup), sorted(s))
466468
self.assertIsNot(dup, s)
467469
self.assertIs(dup.x, s.x)
468470
self.assertIs(dup.z, s.z)
469471
self.assertFalse(hasattr(dup, 'y'))
472+
dup.remove(self.items[0])
473+
self.assertEqual(sorted(dup), sorted(self.items[1:]))
474+
self.assertEqual(sorted(s), sorted(self.items))
470475

471-
dup = copy.deepcopy(s)
476+
dup, dupitems = copy.deepcopy([s, self.items])
472477
self.assertIsInstance(dup, cls)
473478
self.assertEqual(dup, s)
474479
self.assertIsNot(dup, s)
@@ -477,6 +482,45 @@ def test_copying(self):
477482
self.assertEqual(dup.z, s.z)
478483
self.assertIsNot(dup.z, s.z)
479484
self.assertFalse(hasattr(dup, 'y'))
485+
dup.remove(self.items[0])
486+
self.assertEqual(sorted(dup), sorted(self.items[1:]))
487+
self.assertEqual(sorted(s), sorted(self.items))
488+
del dupitems
489+
support.gc_collect() # For PyPy or other GCs.
490+
self.assertEqual(list(dup), [])
491+
self.assertEqual(sorted(s), sorted(self.items))
492+
493+
def test_copying_2(self):
494+
for copyfunc in copy.copy, :#copy.deepcopy:
495+
s = WeakSet()
496+
dup = copyfunc(s)
497+
x = ustr('x')
498+
dup.add(x)
499+
self.assertEqual(len(dup), 1)
500+
self.assertEqual(len(s), 0)
501+
del x
502+
support.gc_collect() # For PyPy or other GCs.
503+
self.assertEqual(len(dup), 0)
504+
505+
def test_pickle(self):
506+
for cls in WeakSet, WeakSetWithSlots:
507+
s = cls(self.items)
508+
s.x = ['x']
509+
s.z = ['z']
510+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
511+
with self.subTest(proto=proto, csl=cls):
512+
pickled = pickle.dumps([s, self.items], protocol=proto)
513+
dup, dupitems = pickle.loads(pickled)
514+
self.assertIsInstance(dup, cls)
515+
self.assertEqual(dup, s)
516+
self.assertEqual(dup.x, s.x)
517+
self.assertEqual(dup.z, s.z)
518+
del dupitems[0]
519+
support.gc_collect() # For PyPy or other GCs.
520+
self.assertEqual(sorted(dup), sorted(self.items[1:]))
521+
del dupitems
522+
support.gc_collect() # For PyPy or other GCs.
523+
self.assertEqual(list(dup), [])
480524

481525

482526
if __name__ == "__main__":
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix copying and pickling :class:`weakref.WeakSet`.

0 commit comments

Comments
 (0)