-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
238 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import gc | ||
import unittest | ||
|
||
import six | ||
|
||
from ..weakiddict import WeakIDDict, WeakIDKeyDict | ||
|
||
|
||
class AllTheSame(object): | ||
def __hash__(self): | ||
return 42 | ||
|
||
def __eq__(self, other): | ||
return isinstance(other, type(self)) | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
|
||
class WeakreffableInt(object): | ||
def __init__(self, value): | ||
self.value = value | ||
|
||
def __hash__(self): | ||
return hash(self.value) | ||
|
||
def __eq__(self, other): | ||
if isinstance(other, int): | ||
return self.value == other | ||
else: | ||
return self.value == other.value | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
|
||
class TestWeakIDDict(unittest.TestCase): | ||
if six.PY2: | ||
assertCountEqual = unittest.TestCase.assertItemsEqual | ||
|
||
def test_weak_keys(self): | ||
wd = WeakIDKeyDict() | ||
|
||
keep = [] | ||
dont_keep = [] | ||
values = list(range(10)) | ||
for n, i in enumerate(values, 1): | ||
key = AllTheSame() | ||
if not (i % 2): | ||
keep.append(key) | ||
else: | ||
dont_keep.append(key) | ||
wd[key] = i | ||
del key | ||
# No keys or values have been deleted, yet. | ||
self.assertEqual(len(wd), n) | ||
|
||
# Delete half of the keys. | ||
self.assertEqual(len(wd), 10) | ||
del dont_keep | ||
self.assertEqual(len(wd), 5) | ||
|
||
# Check the remaining values. | ||
self.assertCountEqual( | ||
list(wd.values()), | ||
list(range(0, 10, 2))) | ||
self.assertEqual( | ||
[wd[k] for k in keep], | ||
list(range(0, 10, 2))) | ||
|
||
# Check the remaining keys. | ||
self.assertCountEqual( | ||
[id(k) for k in wd.keys()], | ||
[id(k) for k in wd]) | ||
self.assertCountEqual( | ||
[id(k) for k in wd.keys()], | ||
[id(k) for k in keep]) | ||
|
||
def test_weak_keys_values(self): | ||
wd = WeakIDDict() | ||
|
||
keep = [] | ||
dont_keep = [] | ||
values = list(map(WeakreffableInt, range(10))) | ||
for n, i in enumerate(values, 1): | ||
key = AllTheSame() | ||
if not (i.value % 2): | ||
keep.append(key) | ||
else: | ||
dont_keep.append(key) | ||
wd[key] = i | ||
del key | ||
# No keys or values have been deleted, yet. | ||
self.assertEqual(len(wd), n) | ||
|
||
# Delete half of the keys. | ||
self.assertEqual(len(wd), 10) | ||
del dont_keep | ||
self.assertEqual(len(wd), 5) | ||
|
||
# Check the remaining values. | ||
self.assertCountEqual( | ||
list(wd.values()), | ||
list(map(WeakreffableInt, [0, 2, 4, 6, 8]))) | ||
self.assertEqual( | ||
[wd[k] for k in keep], | ||
list(map(WeakreffableInt, [0, 2, 4, 6, 8]))) | ||
|
||
# Check the remaining keys. | ||
self.assertCountEqual( | ||
[id(k) for k in wd.keys()], | ||
[id(k) for k in wd]) | ||
self.assertCountEqual( | ||
[id(k) for k in wd.keys()], | ||
[id(k) for k in keep]) | ||
|
||
# Delete the weak values progressively and ensure that the | ||
# corresponding entries disappear. | ||
del values[0:2] | ||
self.assertEqual(len(wd), 4) | ||
del values[0:2] | ||
self.assertEqual(len(wd), 3) | ||
del values[0:2] | ||
self.assertEqual(len(wd), 2) | ||
del values[0:2] | ||
self.assertEqual(len(wd), 1) | ||
del values[0:2] | ||
self.assertEqual(len(wd), 0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import collections | ||
from weakref import ref | ||
|
||
|
||
def _remover(key_id, id_dict_ref): | ||
def callback(wr, id_dict_ref=id_dict_ref): | ||
id_dict = id_dict_ref() | ||
if id_dict is not None: | ||
id_dict.data.pop(key_id, None) | ||
return callback | ||
|
||
|
||
class WeakIDDict(collections.MutableMapping): | ||
""" Make a weak-key-value dictionary that uses the id() of the key for | ||
comparisons. | ||
""" | ||
|
||
def __init__(self, dict=None): | ||
self.data = {} | ||
if dict is not None: | ||
self.update(dict) | ||
|
||
def __repr__(self): | ||
return '<WeakIDDict at 0x{0:x}>'.format(id(self)) | ||
|
||
def __delitem__(self, key): | ||
del self.data[id(key)] | ||
|
||
def __getitem__(self, key): | ||
return self.data[id(key)][1]() | ||
|
||
def __setitem__(self, key, value): | ||
self.data[id(key)] = ( | ||
ref(key, _remover(id(key), ref(self))), | ||
ref(value, _remover(id(key), ref(self)))) | ||
|
||
def __len__(self): | ||
return len(self.data) | ||
|
||
def __contains__(self, key): | ||
return id(key) in self.data | ||
|
||
def __iter__(self): | ||
for id_key in self.data: | ||
wr_key = self.data[id_key][0] | ||
key = wr_key() | ||
if key is not None: | ||
yield key | ||
|
||
|
||
class WeakIDKeyDict(WeakIDDict): | ||
""" Make a weak-key dictionary that uses the id() of the key for | ||
comparisons. | ||
This differs from `WeakIDDict` in that it does not try to make a weakref to | ||
the values. | ||
""" | ||
|
||
def __getitem__(self, key): | ||
return self.data[id(key)][1] | ||
|
||
def __setitem__(self, key, value): | ||
self.data[id(key)] = ( | ||
ref(key, _remover(id(key), ref(self))), | ||
value) |