326 lines
11 KiB
Python
326 lines
11 KiB
Python
# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
|
|
# Released under the New BSD license.
|
|
"""
|
|
This module contains a base type which provides list-style mutations
|
|
without specific data storage methods.
|
|
|
|
See also http://www.aryehleib.com/MutableLists.html
|
|
|
|
Author: Aryeh Leib Taurog.
|
|
"""
|
|
from django.utils.functional import total_ordering
|
|
from django.utils import six
|
|
from django.utils.six.moves import xrange
|
|
|
|
@total_ordering
|
|
class ListMixin(object):
|
|
"""
|
|
A base class which provides complete list interface.
|
|
Derived classes must call ListMixin's __init__() function
|
|
and implement the following:
|
|
|
|
function _get_single_external(self, i):
|
|
Return single item with index i for general use.
|
|
The index i will always satisfy 0 <= i < len(self).
|
|
|
|
function _get_single_internal(self, i):
|
|
Same as above, but for use within the class [Optional]
|
|
Note that if _get_single_internal and _get_single_internal return
|
|
different types of objects, _set_list must distinguish
|
|
between the two and handle each appropriately.
|
|
|
|
function _set_list(self, length, items):
|
|
Recreate the entire object.
|
|
|
|
NOTE: items may be a generator which calls _get_single_internal.
|
|
Therefore, it is necessary to cache the values in a temporary:
|
|
temp = list(items)
|
|
before clobbering the original storage.
|
|
|
|
function _set_single(self, i, value):
|
|
Set the single item at index i to value [Optional]
|
|
If left undefined, all mutations will result in rebuilding
|
|
the object using _set_list.
|
|
|
|
function __len__(self):
|
|
Return the length
|
|
|
|
int _minlength:
|
|
The minimum legal length [Optional]
|
|
|
|
int _maxlength:
|
|
The maximum legal length [Optional]
|
|
|
|
type or tuple _allowed:
|
|
A type or tuple of allowed item types [Optional]
|
|
|
|
class _IndexError:
|
|
The type of exception to be raise on invalid index [Optional]
|
|
"""
|
|
|
|
_minlength = 0
|
|
_maxlength = None
|
|
_IndexError = IndexError
|
|
|
|
### Python initialization and special list interface methods ###
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
if not hasattr(self, '_get_single_internal'):
|
|
self._get_single_internal = self._get_single_external
|
|
|
|
if not hasattr(self, '_set_single'):
|
|
self._set_single = self._set_single_rebuild
|
|
self._assign_extended_slice = self._assign_extended_slice_rebuild
|
|
|
|
super(ListMixin, self).__init__(*args, **kwargs)
|
|
|
|
def __getitem__(self, index):
|
|
"Get the item(s) at the specified index/slice."
|
|
if isinstance(index, slice):
|
|
return [self._get_single_external(i) for i in xrange(*index.indices(len(self)))]
|
|
else:
|
|
index = self._checkindex(index)
|
|
return self._get_single_external(index)
|
|
|
|
def __delitem__(self, index):
|
|
"Delete the item(s) at the specified index/slice."
|
|
if not isinstance(index, six.integer_types + (slice,)):
|
|
raise TypeError("%s is not a legal index" % index)
|
|
|
|
# calculate new length and dimensions
|
|
origLen = len(self)
|
|
if isinstance(index, six.integer_types):
|
|
index = self._checkindex(index)
|
|
indexRange = [index]
|
|
else:
|
|
indexRange = range(*index.indices(origLen))
|
|
|
|
newLen = origLen - len(indexRange)
|
|
newItems = ( self._get_single_internal(i)
|
|
for i in xrange(origLen)
|
|
if i not in indexRange )
|
|
|
|
self._rebuild(newLen, newItems)
|
|
|
|
def __setitem__(self, index, val):
|
|
"Set the item(s) at the specified index/slice."
|
|
if isinstance(index, slice):
|
|
self._set_slice(index, val)
|
|
else:
|
|
index = self._checkindex(index)
|
|
self._check_allowed((val,))
|
|
self._set_single(index, val)
|
|
|
|
def __iter__(self):
|
|
"Iterate over the items in the list"
|
|
for i in xrange(len(self)):
|
|
yield self[i]
|
|
|
|
### Special methods for arithmetic operations ###
|
|
def __add__(self, other):
|
|
'add another list-like object'
|
|
return self.__class__(list(self) + list(other))
|
|
|
|
def __radd__(self, other):
|
|
'add to another list-like object'
|
|
return other.__class__(list(other) + list(self))
|
|
|
|
def __iadd__(self, other):
|
|
'add another list-like object to self'
|
|
self.extend(list(other))
|
|
return self
|
|
|
|
def __mul__(self, n):
|
|
'multiply'
|
|
return self.__class__(list(self) * n)
|
|
|
|
def __rmul__(self, n):
|
|
'multiply'
|
|
return self.__class__(list(self) * n)
|
|
|
|
def __imul__(self, n):
|
|
'multiply'
|
|
if n <= 0:
|
|
del self[:]
|
|
else:
|
|
cache = list(self)
|
|
for i in range(n-1):
|
|
self.extend(cache)
|
|
return self
|
|
|
|
def __eq__(self, other):
|
|
for i in range(len(self)):
|
|
try:
|
|
c = self[i] == other[i]
|
|
except IndexError:
|
|
# must be other is shorter
|
|
return False
|
|
if not c:
|
|
return False
|
|
return True
|
|
|
|
def __lt__(self, other):
|
|
slen = len(self)
|
|
for i in range(slen):
|
|
try:
|
|
c = self[i] < other[i]
|
|
except IndexError:
|
|
# must be other is shorter
|
|
return False
|
|
if c:
|
|
return c
|
|
return slen < len(other)
|
|
|
|
### Public list interface Methods ###
|
|
## Non-mutating ##
|
|
def count(self, val):
|
|
"Standard list count method"
|
|
count = 0
|
|
for i in self:
|
|
if val == i: count += 1
|
|
return count
|
|
|
|
def index(self, val):
|
|
"Standard list index method"
|
|
for i in xrange(0, len(self)):
|
|
if self[i] == val: return i
|
|
raise ValueError('%s not found in object' % str(val))
|
|
|
|
## Mutating ##
|
|
def append(self, val):
|
|
"Standard list append method"
|
|
self[len(self):] = [val]
|
|
|
|
def extend(self, vals):
|
|
"Standard list extend method"
|
|
self[len(self):] = vals
|
|
|
|
def insert(self, index, val):
|
|
"Standard list insert method"
|
|
if not isinstance(index, six.integer_types):
|
|
raise TypeError("%s is not a legal index" % index)
|
|
self[index:index] = [val]
|
|
|
|
def pop(self, index=-1):
|
|
"Standard list pop method"
|
|
result = self[index]
|
|
del self[index]
|
|
return result
|
|
|
|
def remove(self, val):
|
|
"Standard list remove method"
|
|
del self[self.index(val)]
|
|
|
|
def reverse(self):
|
|
"Standard list reverse method"
|
|
self[:] = self[-1::-1]
|
|
|
|
def sort(self, cmp=None, key=None, reverse=False):
|
|
"Standard list sort method"
|
|
if key:
|
|
temp = [(key(v),v) for v in self]
|
|
temp.sort(key=lambda x: x[0], reverse=reverse)
|
|
self[:] = [v[1] for v in temp]
|
|
else:
|
|
temp = list(self)
|
|
if cmp is not None:
|
|
temp.sort(cmp=cmp, reverse=reverse)
|
|
else:
|
|
temp.sort(reverse=reverse)
|
|
self[:] = temp
|
|
|
|
### Private routines ###
|
|
def _rebuild(self, newLen, newItems):
|
|
if newLen < self._minlength:
|
|
raise ValueError('Must have at least %d items' % self._minlength)
|
|
if self._maxlength is not None and newLen > self._maxlength:
|
|
raise ValueError('Cannot have more than %d items' % self._maxlength)
|
|
|
|
self._set_list(newLen, newItems)
|
|
|
|
def _set_single_rebuild(self, index, value):
|
|
self._set_slice(slice(index, index + 1, 1), [value])
|
|
|
|
def _checkindex(self, index, correct=True):
|
|
length = len(self)
|
|
if 0 <= index < length:
|
|
return index
|
|
if correct and -length <= index < 0:
|
|
return index + length
|
|
raise self._IndexError('invalid index: %s' % str(index))
|
|
|
|
def _check_allowed(self, items):
|
|
if hasattr(self, '_allowed'):
|
|
if False in [isinstance(val, self._allowed) for val in items]:
|
|
raise TypeError('Invalid type encountered in the arguments.')
|
|
|
|
def _set_slice(self, index, values):
|
|
"Assign values to a slice of the object"
|
|
try:
|
|
iter(values)
|
|
except TypeError:
|
|
raise TypeError('can only assign an iterable to a slice')
|
|
|
|
self._check_allowed(values)
|
|
|
|
origLen = len(self)
|
|
valueList = list(values)
|
|
start, stop, step = index.indices(origLen)
|
|
|
|
# CAREFUL: index.step and step are not the same!
|
|
# step will never be None
|
|
if index.step is None:
|
|
self._assign_simple_slice(start, stop, valueList)
|
|
else:
|
|
self._assign_extended_slice(start, stop, step, valueList)
|
|
|
|
def _assign_extended_slice_rebuild(self, start, stop, step, valueList):
|
|
'Assign an extended slice by rebuilding entire list'
|
|
indexList = range(start, stop, step)
|
|
# extended slice, only allow assigning slice of same size
|
|
if len(valueList) != len(indexList):
|
|
raise ValueError('attempt to assign sequence of size %d '
|
|
'to extended slice of size %d'
|
|
% (len(valueList), len(indexList)))
|
|
|
|
# we're not changing the length of the sequence
|
|
newLen = len(self)
|
|
newVals = dict(zip(indexList, valueList))
|
|
def newItems():
|
|
for i in xrange(newLen):
|
|
if i in newVals:
|
|
yield newVals[i]
|
|
else:
|
|
yield self._get_single_internal(i)
|
|
|
|
self._rebuild(newLen, newItems())
|
|
|
|
def _assign_extended_slice(self, start, stop, step, valueList):
|
|
'Assign an extended slice by re-assigning individual items'
|
|
indexList = range(start, stop, step)
|
|
# extended slice, only allow assigning slice of same size
|
|
if len(valueList) != len(indexList):
|
|
raise ValueError('attempt to assign sequence of size %d '
|
|
'to extended slice of size %d'
|
|
% (len(valueList), len(indexList)))
|
|
|
|
for i, val in zip(indexList, valueList):
|
|
self._set_single(i, val)
|
|
|
|
def _assign_simple_slice(self, start, stop, valueList):
|
|
'Assign a simple slice; Can assign slice of any length'
|
|
origLen = len(self)
|
|
stop = max(start, stop)
|
|
newLen = origLen - stop + start + len(valueList)
|
|
def newItems():
|
|
for i in xrange(origLen + 1):
|
|
if i == start:
|
|
for val in valueList:
|
|
yield val
|
|
|
|
if i < origLen:
|
|
if i < start or i >= stop:
|
|
yield self._get_single_internal(i)
|
|
|
|
self._rebuild(newLen, newItems())
|