diff --git a/AUTHORS b/AUTHORS index 247e81c14c..1bfb3ad975 100644 --- a/AUTHORS +++ b/AUTHORS @@ -408,6 +408,7 @@ answer newbie questions, and generally made Django that much better: Christian Tanzer Tyler Tarabula Tyson Tate + Aryeh Leib Taurog Frank Tegtmeyer Marcel Telka Terry Huang diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py index 041c3c458f..e49dd8740d 100644 --- a/django/contrib/gis/geos/collections.py +++ b/django/contrib/gis/geos/collections.py @@ -49,8 +49,7 @@ class GeometryCollection(GEOSGeometry): return self.num_geom ### Methods for compatibility with ListMixin ### - @classmethod - def _create_collection(cls, length, items): + def _create_collection(self, length, items): # Creating the geometry pointer array. geoms = get_pointer_arr(length) for i, g in enumerate(items): @@ -58,17 +57,17 @@ class GeometryCollection(GEOSGeometry): # allow GEOSGeometry types (python wrappers) or pointer types geoms[i] = capi.geom_clone(getattr(g, 'ptr', g)) - return capi.create_collection(c_int(cls._typeid), byref(geoms), c_uint(length)) + return capi.create_collection(c_int(self._typeid), byref(geoms), c_uint(length)) - def _getitem_internal(self, index): + def _get_single_internal(self, index): return capi.get_geomn(self.ptr, index) - def _getitem_external(self, index): + def _get_single_external(self, index): "Returns the Geometry from this Collection at the given index (0-based)." # Checking the index and returning the corresponding GEOS geometry. - return GEOSGeometry(capi.geom_clone(self._getitem_internal(index)), srid=self.srid) + return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid) - def _set_collection(self, length, items): + def _set_list(self, length, items): "Create a new collection, and destroy the contents of the previous pointer." prev_ptr = self.ptr srid = self.srid @@ -76,6 +75,9 @@ class GeometryCollection(GEOSGeometry): if srid: self.srid = srid capi.destroy_geom(prev_ptr) + _set_single = GEOSGeometry._set_single_rebuild + _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild + @property def kml(self): "Returns the KML for this Geometry Collection." diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 929746b0ec..866a852d49 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -90,7 +90,6 @@ class GEOSGeometry(GEOSBase, ListMixin): # Post-initialization setup. self._post_init(srid) - super(GEOSGeometry, self).__init__() def _post_init(self, srid): "Helper routine for performing post-initialization setup." diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py index db5e358425..ecf774145e 100644 --- a/django/contrib/gis/geos/linestring.py +++ b/django/contrib/gis/geos/linestring.py @@ -74,12 +74,12 @@ class LineString(GEOSGeometry): "Returns the number of points in this LineString." return len(self._cs) - def _getitem_external(self, index): - self._checkindex(index) + def _get_single_external(self, index): return self._cs[index] - _getitem_internal = _getitem_external - def _set_collection(self, length, items): + _get_single_internal = _get_single_external + + def _set_list(self, length, items): ndim = self._cs.dims # hasz = self._cs.hasz # I don't understand why these are different diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index fc150d5bae..cc28147661 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -2,45 +2,52 @@ # Released under the New BSD license. """ This module contains a base type which provides list-style mutations -This is akin to UserList, but without specific data storage methods. -Possible candidate for a more general position in the source tree, -perhaps django.utils +without specific data storage methods. + +See also http://www.aryehleib.com/MutableLists.html Author: Aryeh Leib Taurog. """ class ListMixin(object): """ - A base class which provides complete list interface - derived classes should implement the following: + A base class which provides complete list interface. + Derived classes must call ListMixin's __init__() function + and implement the following: - function _getitem_external(self, i): - Return single item with index i for general use + 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 _getitem_internal(self, i): + 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_collection(self, length, items): - Recreate the entire object + 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_collection. + the object using _set_list. function __len__(self): Return the length - function __iter__(self): - Return an iterator for the object - int _minlength: The minimum legal length [Optional] int _maxlength: The maximum legal length [Optional] - iterable _allowed: - A list of allowed item types [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] @@ -50,11 +57,11 @@ class ListMixin(object): _maxlength = None _IndexError = IndexError - ### Python initialization and list interface methods ### + ### Python initialization and special list interface methods ### def __init__(self, *args, **kwargs): - if not hasattr(self, '_getitem_internal'): - self._getitem_internal = self._getitem_external + 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 @@ -63,15 +70,15 @@ class ListMixin(object): super(ListMixin, self).__init__(*args, **kwargs) def __getitem__(self, index): - "Gets the coordinates of the point(s) at the specified index/slice." + "Get the item(s) at the specified index/slice." if isinstance(index, slice): - return [self._getitem_external(i) for i in xrange(*index.indices(len(self)))] + return [self._get_single_external(i) for i in xrange(*index.indices(len(self)))] else: index = self._checkindex(index) - return self._getitem_external(index) + return self._get_single_external(index) def __delitem__(self, index): - "Delete the point(s) at the specified index/slice." + "Delete the item(s) at the specified index/slice." if not isinstance(index, (int, long, slice)): raise TypeError("%s is not a legal index" % index) @@ -84,14 +91,14 @@ class ListMixin(object): indexRange = range(*index.indices(origLen)) newLen = origLen - len(indexRange) - newItems = ( self._getitem_internal(i) + 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): - "Sets the Geometry at the specified index." + "Set the item(s) at the specified index/slice." if isinstance(index, slice): self._set_slice(index, val) else: @@ -99,7 +106,74 @@ class ListMixin(object): 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 __cmp__(self, other): + 'cmp' + slen = len(self) + for i in range(slen): + try: + c = cmp(self[i], other[i]) + except IndexError: + # must be other is shorter + return 1 + else: + # elements not equal + if c: return c + + return cmp(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] @@ -120,32 +194,33 @@ class ListMixin(object): del self[index] return result - 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)) - def remove(self, val): "Standard list remove method" del self[self.index(val)] - def count(self, val): - "Standard list count method" - count = 0 - for i in self: - if val == i: count += 1 - return count + def reverse(self): + "Standard list reverse method" + self[:] = self[-1::-1] - ### Private API routines unique to ListMixin ### + 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_collection(newLen, newItems) + self._set_list(newLen, newItems) def _set_single_rebuild(self, index, value): self._set_slice(slice(index, index + 1, 1), [value]) @@ -200,7 +275,7 @@ class ListMixin(object): if i in newVals: yield newVals[i] else: - yield self._getitem_internal(i) + yield self._get_single_internal(i) self._rebuild(newLen, newItems()) @@ -229,6 +304,6 @@ class ListMixin(object): if i < origLen: if i < start or i >= stop: - yield self._getitem_internal(i) + yield self._get_single_internal(i) self._rebuild(newLen, newItems()) diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py index 7abe7ed592..5c00a937b3 100644 --- a/django/contrib/gis/geos/point.py +++ b/django/contrib/gis/geos/point.py @@ -5,6 +5,7 @@ from django.contrib.gis.geos import prototypes as capi class Point(GEOSGeometry): _minlength = 2 + _maxlength = 3 def __init__(self, x, y=None, z=None, srid=None): """ @@ -36,7 +37,6 @@ class Point(GEOSGeometry): # createPoint factory. super(Point, self).__init__(point, srid=srid) - @classmethod def _create_point(self, ndim, coords): """ Create a coordinate sequence, set X, Y, [Z], and create point @@ -52,7 +52,7 @@ class Point(GEOSGeometry): return capi.create_point(cs) - def _set_collection(self, length, items): + def _set_list(self, length, items): ptr = self._create_point(length, items) if ptr: capi.destroy_geom(self.ptr) @@ -76,15 +76,15 @@ class Point(GEOSGeometry): if self.hasz: return 3 else: return 2 - def _getitem_external(self, index): - self._checkindex(index) + def _get_single_external(self, index): if index == 0: return self.x elif index == 1: return self.y elif index == 2: return self.z - _getitem_internal = _getitem_external + + _get_single_internal = _get_single_external def get_x(self): "Returns the X component of the Point." diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py index 345f1d7652..92b2e4ce71 100644 --- a/django/contrib/gis/geos/polygon.py +++ b/django/contrib/gis/geos/polygon.py @@ -59,9 +59,8 @@ class Polygon(GEOSGeometry): return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) - ### These classmethods and routines are needed for list-like operation w/ListMixin ### - @classmethod - def _create_polygon(cls, length, items): + ### These routines are needed for list-like operation w/ListMixin ### + def _create_polygon(self, length, items): # Instantiate LinearRing objects if necessary, but don't clone them yet # _construct_ring will throw a TypeError if a parameter isn't a valid ring # If we cloned the pointers here, we wouldn't be able to clean up @@ -71,30 +70,28 @@ class Polygon(GEOSGeometry): if isinstance(r, GEOM_PTR): rings.append(r) else: - rings.append(cls._construct_ring(r)) + rings.append(self._construct_ring(r)) - shell = cls._clone(rings.pop(0)) + shell = self._clone(rings.pop(0)) n_holes = length - 1 if n_holes: holes = get_pointer_arr(n_holes) for i, r in enumerate(rings): - holes[i] = cls._clone(r) + holes[i] = self._clone(r) holes_param = byref(holes) else: holes_param = None return capi.create_polygon(shell, holes_param, c_uint(n_holes)) - @classmethod - def _clone(cls, g): + def _clone(self, g): if isinstance(g, GEOM_PTR): return capi.geom_clone(g) else: return capi.geom_clone(g.ptr) - @classmethod - def _construct_ring(cls, param, msg='Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'): + def _construct_ring(self, param, msg='Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'): "Helper routine for trying to construct a ring from the given parameter." if isinstance(param, LinearRing): return param try: @@ -103,7 +100,7 @@ class Polygon(GEOSGeometry): except TypeError: raise TypeError(msg) - def _set_collection(self, length, items): + def _set_list(self, length, items): # Getting the current pointer, replacing with the newly constructed # geometry, and destroying the old geometry. prev_ptr = self.ptr @@ -112,7 +109,7 @@ class Polygon(GEOSGeometry): if srid: self.srid = srid capi.destroy_geom(prev_ptr) - def _getitem_internal(self, index): + def _get_single_internal(self, index): """ Returns the ring at the specified index. The first index, 0, will always return the exterior ring. Indices > 0 will return the @@ -120,8 +117,8 @@ class Polygon(GEOSGeometry): return the first and second interior ring, respectively). CAREFUL: Internal/External are not the same as Interior/Exterior! - _getitem_internal returns a pointer from the existing geometries for use - internally by the object's methods. _getitem_external returns a clone + _get_single_internal returns a pointer from the existing geometries for use + internally by the object's methods. _get_single_external returns a clone of the same geometry for use by external code. """ if index == 0: @@ -130,8 +127,11 @@ class Polygon(GEOSGeometry): # Getting the interior ring, have to subtract 1 from the index. return capi.get_intring(self.ptr, index-1) - def _getitem_external(self, index): - return GEOSGeometry(capi.geom_clone(self._getitem_internal(index)), srid=self.srid) + def _get_single_external(self, index): + return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid) + + _set_single = GEOSGeometry._set_single_rebuild + _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild #### Polygon Properties #### @property diff --git a/django/contrib/gis/geos/tests/test_geos_mutation.py b/django/contrib/gis/geos/tests/test_geos_mutation.py index 97e7d8dbe2..260a4689a6 100644 --- a/django/contrib/gis/geos/tests/test_geos_mutation.py +++ b/django/contrib/gis/geos/tests/test_geos_mutation.py @@ -45,15 +45,15 @@ class GEOSMutationTest(unittest.TestCase): def test01_PointMutations(self): 'Testing Point mutations' for p in (Point(1,2,3), fromstr('POINT (1 2 3)')): - self.assertEqual(p._getitem_external(1), 2.0, 'Point _getitem_external') + self.assertEqual(p._get_single_external(1), 2.0, 'Point _get_single_external') # _set_single p._set_single(0,100) self.assertEqual(p.coords, (100.0,2.0,3.0), 'Point _set_single') - # _set_collection - p._set_collection(2,(50,3141)) - self.assertEqual(p.coords, (50.0,3141.0), 'Point _set_collection') + # _set_list + p._set_list(2,(50,3141)) + self.assertEqual(p.coords, (50.0,3141.0), 'Point _set_list') def test02_PointExceptions(self): 'Testing Point exceptions' @@ -72,15 +72,15 @@ class GEOSMutationTest(unittest.TestCase): 'Testing LineString mutations' for ls in (LineString((1,0),(4,1),(6,-1)), fromstr('LINESTRING (1 0,4 1,6 -1)')): - self.assertEqual(ls._getitem_external(1), (4.0,1.0), 'LineString _getitem_external') + self.assertEqual(ls._get_single_external(1), (4.0,1.0), 'LineString _get_single_external') # _set_single ls._set_single(0,(-50,25)) self.assertEqual(ls.coords, ((-50.0,25.0),(4.0,1.0),(6.0,-1.0)), 'LineString _set_single') - # _set_collection - ls._set_collection(2, ((-50.0,25.0),(6.0,-1.0))) - self.assertEqual(ls.coords, ((-50.0,25.0),(6.0,-1.0)), 'LineString _set_collection') + # _set_list + ls._set_list(2, ((-50.0,25.0),(6.0,-1.0))) + self.assertEqual(ls.coords, ((-50.0,25.0),(6.0,-1.0)), 'LineString _set_list') lsa = LineString(ls.coords) for f in geos_function_tests: @@ -91,20 +91,20 @@ class GEOSMutationTest(unittest.TestCase): for pg in (Polygon(((1,0),(4,1),(6,-1),(8,10),(1,0)), ((5,4),(6,4),(6,3),(5,4))), fromstr('POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))')): - self.assertEqual(pg._getitem_external(0), + self.assertEqual(pg._get_single_external(0), LinearRing((1,0),(4,1),(6,-1),(8,10),(1,0)), - 'Polygon _getitem_external(0)') - self.assertEqual(pg._getitem_external(1), + 'Polygon _get_single_external(0)') + self.assertEqual(pg._get_single_external(1), LinearRing((5,4),(6,4),(6,3),(5,4)), - 'Polygon _getitem_external(1)') + 'Polygon _get_single_external(1)') - # _set_collection - pg._set_collection(2, (((1,2),(10,0),(12,9),(-1,15),(1,2)), + # _set_list + pg._set_list(2, (((1,2),(10,0),(12,9),(-1,15),(1,2)), ((4,2),(5,2),(5,3),(4,2)))) self.assertEqual(pg.coords, (((1.0,2.0),(10.0,0.0),(12.0,9.0),(-1.0,15.0),(1.0,2.0)), ((4.0,2.0),(5.0,2.0),(5.0,3.0),(4.0,2.0))), - 'Polygon _set_collection') + 'Polygon _set_list') lsa = Polygon(*pg.coords) for f in geos_function_tests: @@ -114,10 +114,10 @@ class GEOSMutationTest(unittest.TestCase): 'Testing Collection mutations' for mp in (MultiPoint(*map(Point,((3,4),(-1,2),(5,-4),(2,8)))), fromstr('MULTIPOINT (3 4,-1 2,5 -4,2 8)')): - self.assertEqual(mp._getitem_external(2), Point(5,-4), 'Collection _getitem_external') + self.assertEqual(mp._get_single_external(2), Point(5,-4), 'Collection _get_single_external') - mp._set_collection(3, map(Point,((5,5),(3,-2),(8,1)))) - self.assertEqual(mp.coords, ((5.0,5.0),(3.0,-2.0),(8.0,1.0)), 'Collection _set_collection') + mp._set_list(3, map(Point,((5,5),(3,-2),(8,1)))) + self.assertEqual(mp.coords, ((5.0,5.0),(3.0,-2.0),(8.0,1.0)), 'Collection _set_list') lsa = MultiPoint(*map(Point,((5,5),(3,-2),(8,1)))) for f in geos_function_tests: diff --git a/django/contrib/gis/geos/tests/test_mutable_list.py b/django/contrib/gis/geos/tests/test_mutable_list.py index 34b3a4c200..ebbe8ff94a 100644 --- a/django/contrib/gis/geos/tests/test_mutable_list.py +++ b/django/contrib/gis/geos/tests/test_mutable_list.py @@ -1,4 +1,6 @@ -# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved. +# Copyright (c) 2008-2009 Aryeh Leib Taurog, http://www.aryehleib.com +# All rights reserved. +# # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. import unittest @@ -12,13 +14,11 @@ class UserListA(ListMixin): def __len__(self): return len(self._list) - def __iter__(self): return iter(self._list) - def __str__(self): return str(self._list) def __repr__(self): return repr(self._list) - def _set_collection(self, length, items): + def _set_list(self, length, items): # this would work: # self._list = self._mytype(items) # but then we wouldn't be testing length parameter @@ -28,13 +28,9 @@ class UserListA(ListMixin): self._list = self._mytype(itemList) - def _getitem_external(self, index): + def _get_single_external(self, index): return self._list[index] - _getitem_internal = _getitem_external - _set_single = ListMixin._set_single_rebuild - _assign_extended_slice = ListMixin._assign_extended_slice_rebuild - class UserListB(UserListA): _mytype = list @@ -55,22 +51,19 @@ class ListMixinTest(unittest.TestCase): limit = 3 listType = UserListA - @classmethod - def lists_of_len(cls, length=None): - if length is None: length = cls.limit + def lists_of_len(self, length=None): + if length is None: length = self.limit pl = range(length) - return pl, cls.listType(pl) + return pl, self.listType(pl) - @classmethod - def limits_plus(cls, b): - return range(-cls.limit - b, cls.limit + b) + def limits_plus(self, b): + return range(-self.limit - b, self.limit + b) - @classmethod - def step_range(cls): - return range(-1 - cls.limit, 0) + range(1, 1 + cls.limit) + def step_range(self): + return range(-1 - self.limit, 0) + range(1, 1 + self.limit) def test01_getslice(self): - 'Testing slice retrieval' + 'Slice retrieval' pl, ul = self.lists_of_len() for i in self.limits_plus(1): self.assertEqual(pl[i:], ul[i:], 'slice [%d:]' % (i)) @@ -89,7 +82,7 @@ class ListMixinTest(unittest.TestCase): self.assertEqual(pl[::k], ul[::k], 'slice [::%d]' % (k)) def test02_setslice(self): - 'Testing slice assignment' + 'Slice assignment' def setfcn(x,i,j,k,L): x[i:j:k] = range(L) pl, ul = self.lists_of_len() for slen in range(self.limit + 1): @@ -145,7 +138,7 @@ class ListMixinTest(unittest.TestCase): def test03_delslice(self): - 'Testing delete slice' + 'Delete slice' for Len in range(self.limit): pl, ul = self.lists_of_len(Len) del pl[:] @@ -189,7 +182,7 @@ class ListMixinTest(unittest.TestCase): self.assertEqual(pl[:], ul[:], 'del slice [::%d]' % (k)) def test04_get_set_del_single(self): - 'Testing get/set/delete single item' + 'Get/set/delete single item' pl, ul = self.lists_of_len() for i in self.limits_plus(0): self.assertEqual(pl[i], ul[i], 'get single item [%d]' % i) @@ -207,7 +200,7 @@ class ListMixinTest(unittest.TestCase): self.assertEqual(pl[:], ul[:], 'del single item [%d]' % i) def test05_out_of_range_exceptions(self): - 'Testing out of range exceptions' + 'Out of range exceptions' def setfcn(x, i): x[i] = 20 def getfcn(x, i): return x[i] def delfcn(x, i): del x[i] @@ -218,7 +211,7 @@ class ListMixinTest(unittest.TestCase): self.assertRaises(IndexError, delfcn, ul, i) # 'del index %d' % i) def test06_list_methods(self): - 'Testing list methods' + 'List methods' pl, ul = self.lists_of_len() pl.append(40) ul.append(40) @@ -228,6 +221,10 @@ class ListMixinTest(unittest.TestCase): ul.extend(range(50,55)) self.assertEqual(pl[:], ul[:], 'extend') + pl.reverse() + ul.reverse() + self.assertEqual(pl[:], ul[:], 'reverse') + for i in self.limits_plus(1): pl, ul = self.lists_of_len() pl.insert(i,50) @@ -267,7 +264,7 @@ class ListMixinTest(unittest.TestCase): self.assertRaises(ValueError, removefcn, ul, 40) def test07_allowed_types(self): - 'Testing type-restricted list' + 'Type-restricted list' pl, ul = self.lists_of_len() ul._allowed = (int, long) ul[1] = 50 @@ -277,7 +274,7 @@ class ListMixinTest(unittest.TestCase): self.assertRaises(TypeError, setfcn, ul, slice(0,3,2), ('hello','goodbye')) def test08_min_length(self): - 'Testing length limits' + 'Length limits' pl, ul = self.lists_of_len() ul._minlength = 1 def delfcn(x,i): del x[:i] @@ -293,13 +290,13 @@ class ListMixinTest(unittest.TestCase): self.assertRaises(ValueError, ul.append, 10) def test09_iterable_check(self): - 'Testing error on assigning non-iterable to slice' + 'Error on assigning non-iterable to slice' pl, ul = self.lists_of_len(self.limit + 1) def setfcn(x, i, v): x[i] = v self.assertRaises(TypeError, setfcn, ul, slice(0,3,2), 2) def test10_checkindex(self): - 'Testing index check' + 'Index check' pl, ul = self.lists_of_len() for i in self.limits_plus(0): if i < 0: @@ -313,6 +310,78 @@ class ListMixinTest(unittest.TestCase): ul._IndexError = TypeError self.assertRaises(TypeError, ul._checkindex, -self.limit - 1) + def test_11_sorting(self): + 'Sorting' + pl, ul = self.lists_of_len() + pl.insert(0, pl.pop()) + ul.insert(0, ul.pop()) + pl.sort() + ul.sort() + self.assertEqual(pl[:], ul[:], 'sort') + mid = pl[len(pl) / 2] + pl.sort(key=lambda x: (mid-x)**2) + ul.sort(key=lambda x: (mid-x)**2) + self.assertEqual(pl[:], ul[:], 'sort w/ key') + + pl.insert(0, pl.pop()) + ul.insert(0, ul.pop()) + pl.sort(reverse=True) + ul.sort(reverse=True) + self.assertEqual(pl[:], ul[:], 'sort w/ reverse') + mid = pl[len(pl) / 2] + pl.sort(key=lambda x: (mid-x)**2) + ul.sort(key=lambda x: (mid-x)**2) + self.assertEqual(pl[:], ul[:], 'sort w/ key') + + def test_12_arithmetic(self): + 'Arithmetic' + pl, ul = self.lists_of_len() + al = range(10,14) + self.assertEqual(list(pl + al), list(ul + al), 'add') + self.assertEqual(type(ul), type(ul + al), 'type of add result') + self.assertEqual(list(al + pl), list(al + ul), 'radd') + self.assertEqual(type(al), type(al + ul), 'type of radd result') + objid = id(ul) + pl += al + ul += al + self.assertEqual(pl[:], ul[:], 'in-place add') + self.assertEqual(objid, id(ul), 'in-place add id') + + for n in (-1,0,1,3): + pl, ul = self.lists_of_len() + self.assertEqual(list(pl * n), list(ul * n), 'mul by %d' % n) + self.assertEqual(type(ul), type(ul * n), 'type of mul by %d result' % n) + self.assertEqual(list(n * pl), list(n * ul), 'rmul by %d' % n) + self.assertEqual(type(ul), type(n * ul), 'type of rmul by %d result' % n) + objid = id(ul) + pl *= n + ul *= n + self.assertEqual(pl[:], ul[:], 'in-place mul by %d' % n) + self.assertEqual(objid, id(ul), 'in-place mul by %d id' % n) + + pl, ul = self.lists_of_len() + self.assertEqual(pl, ul, 'cmp for equal') + self.assert_(pl >= ul, 'cmp for gte self') + self.assert_(pl <= ul, 'cmp for lte self') + self.assert_(ul >= pl, 'cmp for self gte') + self.assert_(ul <= pl, 'cmp for self lte') + + self.assert_(pl + [5] > ul, 'cmp') + self.assert_(pl + [5] >= ul, 'cmp') + self.assert_(pl < ul + [2], 'cmp') + self.assert_(pl <= ul + [2], 'cmp') + self.assert_(ul + [5] > pl, 'cmp') + self.assert_(ul + [5] >= pl, 'cmp') + self.assert_(ul < pl + [2], 'cmp') + self.assert_(ul <= pl + [2], 'cmp') + + pl[1] = 20 + self.assert_(pl > ul, 'cmp for gt self') + self.assert_(ul < pl, 'cmp for self lt') + pl[1] = -20 + self.assert_(pl < ul, 'cmp for lt self') + self.assert_(pl < ul, 'cmp for lt self') + class ListMixinTestSingle(ListMixinTest): listType = UserListB