UPDATE: Thanks everyone for the suggestions! I made a follow up version here which fixes the bugs, incorporates the feedback, and has more tests.
A ReversibleList
behaves like a normal list when ReversibleList.reversed == False
, but it behaves as if it had been reversed when ReversibleList.reversed == True
. The trick is that the underlying list is never actually reversed. This can be a boon for performance [citation needed] if whatever one's doing requires reversing and unreversing a particular list many times, or reversing many lists.
Any suggestions for improvement are welcome! Interested in whether my subclass is properly written and whether the concept makes sense to you/is self-consistent. I have overridden only a few of list
's method - just enough for my use case and I'm aware there may be other list
methods I may need to override in the future for expanded use cases.
Thanks in advanced for any suggestions!
Implementation
class ReversibleList(list):
def reverse(self):
self.reversed = True
def forwardAll(self):
self.reversed = False
def toggleDirectionl(self):
self.reversed = not self.reversed
def __init__(self, iterable):
super().__init__(iterable)
self.reversed = False
def __getitem__(self, key):
if self.reversed:
key = self._getReverseKey(key)
if isinstance(key, slice):
return ReversibleList(super().__getitem__(key))
return super().__getitem__(key)
def __setitem__(self, key, val):
if self.reversed:
key = self._getReverseKey(key)
if isinstance(key, slice) and not isinstance(val, type(self)):
super().__setitem__(key, reversed(val))
return
super().__setitem__(key, val)
def __delitem__(self, key):
if self.reversed:
key = self._getReverseKey(key)
return super().__delitem__(key)
def __str__(self):
if self.reversed:
return '[' + ', '.join(map(str, reversed(self))) + ']'
return super().__str__()
def __repr__(self):
return f'ReversibleList({super().__repr__()})'
@classmethod
def _invalidKeyTypeErrorMsg(cls, key):
return f'list indices must be integers or slices, not {type(key).__name__}' # same error one gets when doing [0,1,2][1, 2]
def _getReverseKey(self, key):
if isinstance(key, slice):
# PRECONDITION: key.step == 1
N = len(self)
i, j, _ = key.indices(len(self))
assert i >= 0 and j >= 0, 'I assumed that slice.indices returns positive values for start and stop, but was wrong.'
i = i or -N
j = j or -N
return slice(-j, -i)
if not isinstance(key, int):
raise TypeError(type(self)._invalidKeyTypeErrorMsg('key'))
return -1-key
Usage
a = ReversibleList([0, 1, 2, 3, 4, 5])
print(a[1]) # 1
print(a) # [0, 1, 2, 3, 4, 5]
print(a[:]) # [0, 1, 2, 3, 4, 5]
print(repr(a)) # ReversibleList([0, 1, 2, 3, 4, 5])
a.reverse()
print(a[1]) # 4
print(a) # [5, 4, 3, 2, 1, 0]
print(a[:]) # [5, 4, 3, 2, 1, 0]
print(repr(a)) # ReversibleList([0, 1, 2, 3, 4, 5])
a[:] = a
print(a) # [5, 4, 3, 2, 1, 0]
a[2:5] = a[2:5]
print(a) # [5, 4, 3, 2, 1, 0]
print(a[2:5]) # [3, 2, 1]
a[2:5] = [3, 2, 1]
print(a) # [5, 4, 3, 2, 1, 0]
a[2:5] = ReversibleList([1, 2, 3])
print(a) # [5, 4, 3, 2, 1, 0]