Issue 28014: Strange interaction between methods in subclass of C OrderedDict (original) (raw)

I'm not certain that the implementation of this subclass of OrderedDict is actually sane, but it works in 3.4 and fails in 3.5+.

The buggy implementation:

class SimpleLRUCache(OrderedDict):

def __init__(self, size):
    super().__init__()
    self.size = size

def __getitem__(self, item):
    value = super().__getitem__(item)
    self.move_to_end(item)
    return value

def __setitem__(self, key, value):
    while key not in self and len(self) >= self.size:
        self.popitem(last=False)
    super().__setitem__(key, value)
    self.move_to_end(key)

When trying to add a new item after size items are already in the cache, it will throw a KeyError with the key of the item in the oldest position. Something like:

s = SimpleLRUCache(2) s['t1'] = 1 s['t2'] = 2 s['t2'] # gives 2, properly moves 2 to the end s['t3'] = 3

Traceback (most recent call last): File "", line 1, in File "simple_lru.py", line 14, in setitem self.popitem(last=False) File "simple_lru.py", line 9, in getitem self.move_to_end(item) KeyError: 't3'

I can work around the failure by implementing getitem as follows:

def __getitem__(self, item):
    value = super().__getitem__(item)
    del self[item]
    self[item] = value
    return value

Attached is a script with a couple of tests that pass with 3.4 and fail with 3.5+.

Note: This class doesn't actually work on 3.4 in other ways (because getitem is not idempotent, while OrderedDict assumes it is):

s = SimpleLRUCache(2) s['t1'] = 1 s SimpleLRUCache([('t1', 1)]) s['t2'] = 2 s SimpleLRUCache([('t1', 1)]) s SimpleLRUCache([('t2', 2)]) # <-- No changes, repr different

If your getitem isn't idempotent, you've broken a basic assumption built into the logic of the other methods you inherited, and you're going to need to override other methods to avoid misbehavior.