[Python-checkins] cpython (3.2): Issue #14159: Fix the len() of weak containers (WeakSet, WeakKeyDictionary,
antoine.pitrou
python-checkins at python.org
Thu Mar 1 16:32:28 CET 2012
http://hg.python.org/cpython/rev/1cd0688ff004
changeset: 75356:1cd0688ff004
branch: 3.2
parent: 75348:1c77eadba9dc
user: Antoine Pitrou <solipsis at pitrou.net>
date: Thu Mar 01 16:26:35 2012 +0100
summary:
Issue #14159: Fix the len() of weak containers (WeakSet, WeakKeyDictionary, WeakValueDictionary) to return a better approximation when some objects are dead or dying.
Moreover, the implementation is now O(1) rather than O(n).
Thanks to Yury Selivanov for reporting.
files:
Lib/_weakrefset.py | 2 +-
Lib/test/test_weakref.py | 60 ++++++++++++++++++++++++++++
Lib/test/test_weakset.py | 47 +++++++++++++++++++++
Lib/weakref.py | 4 +-
Misc/NEWS | 5 ++
5 files changed, 115 insertions(+), 3 deletions(-)
diff --git a/Lib/_weakrefset.py b/Lib/_weakrefset.py
--- a/Lib/_weakrefset.py
+++ b/Lib/_weakrefset.py
@@ -63,7 +63,7 @@
yield item
def __len__(self):
- return sum(x() is not None for x in self.data)
+ return len(self.data) - len(self._pending_removals)
def __contains__(self, item):
try:
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -812,11 +812,71 @@
def __hash__(self):
return hash(self.arg)
+class RefCycle:
+ def __init__(self):
+ self.cycle = self
+
class MappingTestCase(TestBase):
COUNT = 10
+ def check_len_cycles(self, dict_type, cons):
+ N = 20
+ items = [RefCycle() for i in range(N)]
+ dct = dict_type(cons(o) for o in items)
+ # Keep an iterator alive
+ it = dct.items()
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ del items
+ gc.collect()
+ n1 = len(dct)
+ del it
+ gc.collect()
+ n2 = len(dct)
+ # one item may be kept alive inside the iterator
+ self.assertIn(n1, (0, 1))
+ self.assertEqual(n2, 0)
+
+ def test_weak_keyed_len_cycles(self):
+ self.check_len_cycles(weakref.WeakKeyDictionary, lambda k: (k, 1))
+
+ def test_weak_valued_len_cycles(self):
+ self.check_len_cycles(weakref.WeakValueDictionary, lambda k: (1, k))
+
+ def check_len_race(self, dict_type, cons):
+ # Extended sanity checks for len() in the face of cyclic collection
+ self.addCleanup(gc.set_threshold, *gc.get_threshold())
+ for th in range(1, 100):
+ N = 20
+ gc.collect(0)
+ gc.set_threshold(th, th, th)
+ items = [RefCycle() for i in range(N)]
+ dct = dict_type(cons(o) for o in items)
+ del items
+ # All items will be collected at next garbage collection pass
+ it = dct.items()
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ n1 = len(dct)
+ del it
+ n2 = len(dct)
+ self.assertGreaterEqual(n1, 0)
+ self.assertLessEqual(n1, N)
+ self.assertGreaterEqual(n2, 0)
+ self.assertLessEqual(n2, n1)
+
+ def test_weak_keyed_len_race(self):
+ self.check_len_race(weakref.WeakKeyDictionary, lambda k: (k, 1))
+
+ def test_weak_valued_len_race(self):
+ self.check_len_race(weakref.WeakValueDictionary, lambda k: (1, k))
+
def test_weak_values(self):
#
# This exercises d.copy(), d.items(), d[], del d[], len(d).
diff --git a/Lib/test/test_weakset.py b/Lib/test/test_weakset.py
--- a/Lib/test/test_weakset.py
+++ b/Lib/test/test_weakset.py
@@ -17,6 +17,10 @@
class Foo:
pass
+class RefCycle:
+ def __init__(self):
+ self.cycle = self
+
class TestWeakSet(unittest.TestCase):
@@ -359,6 +363,49 @@
s.clear()
self.assertEqual(len(s), 0)
+ def test_len_cycles(self):
+ N = 20
+ items = [RefCycle() for i in range(N)]
+ s = WeakSet(items)
+ del items
+ it = iter(s)
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ gc.collect()
+ n1 = len(s)
+ del it
+ gc.collect()
+ n2 = len(s)
+ # one item may be kept alive inside the iterator
+ self.assertIn(n1, (0, 1))
+ self.assertEqual(n2, 0)
+
+ def test_len_race(self):
+ # Extended sanity checks for len() in the face of cyclic collection
+ self.addCleanup(gc.set_threshold, *gc.get_threshold())
+ for th in range(1, 100):
+ N = 20
+ gc.collect(0)
+ gc.set_threshold(th, th, th)
+ items = [RefCycle() for i in range(N)]
+ s = WeakSet(items)
+ del items
+ # All items will be collected at next garbage collection pass
+ it = iter(s)
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ n1 = len(s)
+ del it
+ n2 = len(s)
+ self.assertGreaterEqual(n1, 0)
+ self.assertLessEqual(n1, N)
+ self.assertGreaterEqual(n2, 0)
+ self.assertLessEqual(n2, n1)
+
def test_main(verbose=None):
support.run_unittest(TestWeakSet)
diff --git a/Lib/weakref.py b/Lib/weakref.py
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -78,7 +78,7 @@
del self.data[key]
def __len__(self):
- return sum(wr() is not None for wr in self.data.values())
+ return len(self.data) - len(self._pending_removals)
def __contains__(self, key):
try:
@@ -290,7 +290,7 @@
return self.data[ref(key)]
def __len__(self):
- return len(self.data)
+ return len(self.data) - len(self._pending_removals)
def __repr__(self):
return "<WeakKeyDictionary at %s>" % id(self)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -127,6 +127,11 @@
Library
-------
+- Issue #14159: Fix the len() of weak containers (WeakSet, WeakKeyDictionary,
+ WeakValueDictionary) to return a better approximation when some objects
+ are dead or dying. Moreover, the implementation is now O(1) rather than
+ O(n).
+
- Issue #13125: Silence spurious test_lib2to3 output when in non-verbose mode.
Patch by Mikhail Novikov.
--
Repository URL: http://hg.python.org/cpython
More information about the Python-checkins
mailing list