Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions Lib/test/test_ordered_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,5 +896,88 @@ def test_popitem(self):
self.assertRaises(KeyError, d.popitem)


class SimpleLRUCache:

def __init__(self, size):
super().__init__()
self.size = size
self.counts = dict.fromkeys(('get', 'set', 'del'), 0)

def __getitem__(self, item):
self.counts['get'] += 1
value = super().__getitem__(item)
self.move_to_end(item)
return value

def __setitem__(self, key, value):
self.counts['set'] += 1
while key not in self and len(self) >= self.size:
self.popitem(last=False)
super().__setitem__(key, value)
self.move_to_end(key)

def __delitem__(self, key):
self.counts['del'] += 1
super().__delitem__(key)


class SimpleLRUCacheTests:

def test_add_after_full(self):
c = self.type2test(2)
c['t1'] = 1
c['t2'] = 2
c['t3'] = 3
self.assertEqual(c.counts, {'get': 0, 'set': 3, 'del': 0})
self.assertEqual(list(c), ['t2', 't3'])
self.assertEqual(c.counts, {'get': 0, 'set': 3, 'del': 0})

def test_popitem(self):
c = self.type2test(3)
for i in range(1, 4):
c[i] = i
self.assertEqual(c.popitem(last=False), (1, 1))
self.assertEqual(c.popitem(last=True), (3, 3))
self.assertEqual(c.counts, {'get': 0, 'set': 3, 'del': 0})

def test_pop(self):
c = self.type2test(3)
for i in range(1, 4):
c[i] = i
self.assertEqual(c.counts, {'get': 0, 'set': 3, 'del': 0})
self.assertEqual(c.pop(2), 2)
self.assertEqual(c.counts, {'get': 1, 'set': 3, 'del': 1})
self.assertEqual(c.pop(4, 0), 0)
self.assertEqual(c.counts, {'get': 1, 'set': 3, 'del': 1})
self.assertRaises(KeyError, c.pop, 4)
self.assertEqual(c.counts, {'get': 1, 'set': 3, 'del': 1})

def test_change_order_on_get(self):
c = self.type2test(3)
for i in range(1, 4):
c[i] = i
self.assertEqual(list(c), list(range(1, 4)))
self.assertEqual(c.counts, {'get': 0, 'set': 3, 'del': 0})
self.assertEqual(c[2], 2)
self.assertEqual(c.counts, {'get': 1, 'set': 3, 'del': 0})
self.assertEqual(list(c), [1, 3, 2])


class PySimpleLRUCacheTests(SimpleLRUCacheTests, unittest.TestCase):

class type2test(SimpleLRUCache, py_coll.OrderedDict):
pass


@unittest.skipUnless(c_coll, 'requires the C version of the collections module')
class CSimpleLRUCacheTests(SimpleLRUCacheTests, unittest.TestCase):

@classmethod
def setUpClass(cls):
class type2test(SimpleLRUCache, c_coll.OrderedDict):
pass
cls.type2test = type2test


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Make the C implementation of :meth:`collections.OrderedDict.popitem` in
conform with the pure Python implementation. It no longer calls
``__getitem__`` and ``__delitem__`` methods of the OrderedDict subclasses.
108 changes: 48 additions & 60 deletions Objects/odictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1047,8 +1047,37 @@ OrderedDict_setdefault_impl(PyODictObject *self, PyObject *key,

/* pop() */

/* forward */
static PyObject * _odict_popkey(PyObject *, PyObject *, PyObject *);
static PyObject *
_odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj,
Py_hash_t hash)
{
PyObject *value = NULL;

_ODictNode *node = _odict_find_node_hash((PyODictObject *)od, key, hash);
if (node != NULL) {
/* Pop the node first to avoid a possible dict resize (due to
eval loop reentrancy) and complications due to hash collision
resolution. */
int res = _odict_clear_node((PyODictObject *)od, node, key, hash);
if (res < 0) {
return NULL;
}
/* Now delete the value from the dict. */
value = _PyDict_Pop_KnownHash(od, key, hash, failobj);
}
else if (value == NULL && !PyErr_Occurred()) {
/* Apply the fallback value, if necessary. */
if (failobj) {
value = failobj;
Py_INCREF(failobj);
}
else {
PyErr_SetObject(PyExc_KeyError, key);
}
}

return value;
}

/* Skips __missing__() calls. */
/*[clinic input]
Expand All @@ -1068,63 +1097,32 @@ OrderedDict_pop_impl(PyODictObject *self, PyObject *key,
PyObject *default_value)
/*[clinic end generated code: output=7a6447d104e7494b input=7efe36601007dff7]*/
{
return _odict_popkey((PyObject *)self, key, default_value);
}

static PyObject *
_odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj,
Py_hash_t hash)
{
_ODictNode *node;
PyObject *value = NULL;

/* Pop the node first to avoid a possible dict resize (due to
eval loop reentrancy) and complications due to hash collision
resolution. */
node = _odict_find_node_hash((PyODictObject *)od, key, hash);
if (node == NULL) {
if (PyErr_Occurred())
return NULL;
}
else {
int res = _odict_clear_node((PyODictObject *)od, node, key, hash);
if (res < 0) {
PyObject *od = (PyObject *)self;
if (PyODict_CheckExact(od)) {
Py_hash_t hash = PyObject_Hash(key);
if (hash == -1)
return NULL;
}
return _odict_popkey_hash(od, key, default_value, hash);
}

/* Now delete the value from the dict. */
if (PyODict_CheckExact(od)) {
if (node != NULL) {
value = _PyDict_GetItem_KnownHash(od, key, hash); /* borrowed */
if (value != NULL) {
Py_INCREF(value);
if (_PyDict_DelItem_KnownHash(od, key, hash) < 0) {
Py_DECREF(value);
return NULL;
}
}
}
}
else {
int exists = PySequence_Contains(od, key);
if (exists < 0)
return NULL;
if (exists) {
value = PyObject_GetItem(od, key);
if (value != NULL) {
if (PyObject_DelItem(od, key) == -1) {
Py_CLEAR(value);
}
PyObject *value = NULL;
int exists = PySequence_Contains(od, key);
if (exists < 0)
return NULL;
if (exists) {
value = PyObject_GetItem(od, key);
if (value != NULL) {
if (PyObject_DelItem(od, key) == -1) {
Py_CLEAR(value);
}
}
}

/* Apply the fallback value, if necessary. */
if (value == NULL && !PyErr_Occurred()) {
if (failobj) {
value = failobj;
Py_INCREF(failobj);
if (default_value) {
value = default_value;
Py_INCREF(default_value);
}
else {
PyErr_SetObject(PyExc_KeyError, key);
Expand All @@ -1134,16 +1132,6 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj,
return value;
}

static PyObject *
_odict_popkey(PyObject *od, PyObject *key, PyObject *failobj)
{
Py_hash_t hash = PyObject_Hash(key);
if (hash == -1)
return NULL;

return _odict_popkey_hash(od, key, failobj, hash);
}


/* popitem() */

Expand Down