# 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())