123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- # 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=cmp, key=None, reverse=False):
- "Standard list sort method"
- if key:
- temp = [(key(v),v) for v in self]
- temp.sort(cmp=cmp, key=lambda x: x[0], reverse=reverse)
- self[:] = [v[1] for v in temp]
- else:
- temp = list(self)
- temp.sort(cmp=cmp, 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())
|