| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1310,6 +1310,36 @@ def check_weak_destroy_and_mutate_while_iterating(self, dict, testcontext): | |||
| 1310 | 1310 | dict.clear() | |
| 1311 | 1311 | self.assertEqual(len(dict), 0) | |
| 1312 | 1312 | ||
| 1313 | + def check_weak_del_and_len_while_iterating(self, dict, testcontext): | ||
| 1314 | + # Check that len() works when both iterating and removing keys | ||
| 1315 | + # explicitly through various means (.pop(), .clear()...), while | ||
| 1316 | + # implicit mutation is deferred because an iterator is alive. | ||
| 1317 | + # (each call to testcontext() should schedule one item for removal | ||
| 1318 | + # for this test to work properly) | ||
| 1319 | + o = Object(123456) | ||
| 1320 | + with testcontext(): | ||
| 1321 | + n = len(dict) | ||
| 1322 | + dict.popitem() | ||
| 1323 | + self.assertEqual(len(dict), n - 1) | ||
| 1324 | + dict[o] = o | ||
| 1325 | + self.assertEqual(len(dict), n) | ||
| 1326 | + with testcontext(): | ||
| 1327 | + self.assertEqual(len(dict), n - 1) | ||
| 1328 | + dict.pop(next(dict.keys())) | ||
| 1329 | + self.assertEqual(len(dict), n - 2) | ||
| 1330 | + with testcontext(): | ||
| 1331 | + self.assertEqual(len(dict), n - 3) | ||
| 1332 | + del dict[next(dict.keys())] | ||
| 1333 | + self.assertEqual(len(dict), n - 4) | ||
| 1334 | + with testcontext(): | ||
| 1335 | + self.assertEqual(len(dict), n - 5) | ||
| 1336 | + dict.popitem() | ||
| 1337 | + self.assertEqual(len(dict), n - 6) | ||
| 1338 | + with testcontext(): | ||
| 1339 | + dict.clear() | ||
| 1340 | + self.assertEqual(len(dict), 0) | ||
| 1341 | + self.assertEqual(len(dict), 0) | ||
| 1342 | + | ||
| 1313 | 1343 | def test_weak_keys_destroy_while_iterating(self): | |
| 1314 | 1344 | # Issue #7105: iterators shouldn't crash when a key is implicitly removed | |
| 1315 | 1345 | dict, objects = self.make_weak_keyed_dict() | |
@@ -1331,6 +1361,10 @@ def testcontext(): | |||
| 1331 | 1361 | it = None # should commit all removals | |
| 1332 | 1362 | gc.collect() | |
| 1333 | 1363 | self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext) | |
| 1364 | + # Issue #21173: len() fragile when keys are both implicitly and | ||
| 1365 | + # explicitly removed. | ||
| 1366 | + dict, objects = self.make_weak_keyed_dict() | ||
| 1367 | + self.check_weak_del_and_len_while_iterating(dict, testcontext) | ||
| 1334 | 1368 | ||
| 1335 | 1369 | def test_weak_values_destroy_while_iterating(self): | |
| 1336 | 1370 | # Issue #7105: iterators shouldn't crash when a key is implicitly removed | |
@@ -1354,6 +1388,8 @@ def testcontext(): | |||
| 1354 | 1388 | it = None # should commit all removals | |
| 1355 | 1389 | gc.collect() | |
| 1356 | 1390 | self.check_weak_destroy_and_mutate_while_iterating(dict, testcontext) | |
| 1391 | + dict, objects = self.make_weak_valued_dict() | ||
| 1392 | + self.check_weak_del_and_len_while_iterating(dict, testcontext) | ||
| 1357 | 1393 | ||
| 1358 | 1394 | def test_make_weak_keyed_dict_from_dict(self): | |
| 1359 | 1395 | o = Object(3) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -322,6 +322,7 @@ def remove(k, selfref=ref(self)): | |||
| 322 | 322 | # A list of dead weakrefs (keys to be removed) | |
| 323 | 323 | self._pending_removals = [] | |
| 324 | 324 | self._iterating = set() | |
| 325 | + self._dirty_len = False | ||
| 325 | 326 | if dict is not None: | |
| 326 | 327 | self.update(dict) | |
| 327 | 328 | ||
@@ -338,13 +339,23 @@ def _commit_removals(self): | |||
| 338 | 339 | except KeyError: | |
| 339 | 340 | pass | |
| 340 | 341 | ||
| 342 | + def _scrub_removals(self): | ||
| 343 | + d = self.data | ||
| 344 | + self._pending_removals = [k for k in self._pending_removals if k in d] | ||
| 345 | + self._dirty_len = False | ||
| 346 | + | ||
| 341 | 347 | def __delitem__(self, key): | |
| 348 | + self._dirty_len = True | ||
| 342 | 349 | del self.data[ref(key)] | |
| 343 | 350 | ||
| 344 | 351 | def __getitem__(self, key): | |
| 345 | 352 | return self.data[ref(key)] | |
| 346 | 353 | ||
| 347 | 354 | def __len__(self): | |
| 355 | + if self._dirty_len and self._pending_removals: | ||
| 356 | + # self._pending_removals may still contain keys which were | ||
| 357 | + # explicitly removed, we have to scrub them (see issue #21173). | ||
| 358 | + self._scrub_removals() | ||
| 348 | 359 | return len(self.data) - len(self._pending_removals) | |
| 349 | 360 | ||
| 350 | 361 | def __repr__(self): | |
@@ -417,13 +428,15 @@ def keyrefs(self): | |||
| 417 | 428 | return list(self.data) | |
| 418 | 429 | ||
| 419 | 430 | def popitem(self): | |
| 431 | + self._dirty_len = True | ||
| 420 | 432 | while True: | |
| 421 | 433 | key, value = self.data.popitem() | |
| 422 | 434 | o = key() | |
| 423 | 435 | if o is not None: | |
| 424 | 436 | return o, value | |
| 425 | 437 | ||
| 426 | 438 | def pop(self, key, *args): | |
| 439 | + self._dirty_len = True | ||
| 427 | 440 | return self.data.pop(ref(key), *args) | |
| 428 | 441 | ||
| 429 | 442 | def setdefault(self, key, default=None): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -166,6 +166,9 @@ Core and Builtins | |||
| 166 | 166 | Library | |
| 167 | 167 | ------- | |
| 168 | 168 | ||
| 169 | + - Issue #21173: Fix len() on a WeakKeyDictionary when .clear() was called | ||
| 170 | + with an iterator alive. | ||
| 171 | + | ||
| 169 | 172 | - Issue #11866: Eliminated race condition in the computation of names | |
| 170 | 173 | for new threads. | |
| 171 | 174 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments