From 32ac6958e1618e9025486212dac412346126bccd Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Wed, 27 Mar 2013 20:59:23 -0400 Subject: [PATCH] Apply some bugfixes to SmartList to fix tests on Python 3. - Add a _SliceNormalizerMixIn to properly handle slices. - Use floor division when applying key.step. - Implement sort() without 'cmp' parameter. - Fix bytes(list) behavior. - Children of _ListProxies are now _ListProxies, not regular lists. --- mwparserfromhell/smart_list.py | 137 +++++++++++++++++++++++++++-------------- tests/test_smart_list.py | 12 ++-- 2 files changed, 99 insertions(+), 50 deletions(-) diff --git a/mwparserfromhell/smart_list.py b/mwparserfromhell/smart_list.py index 46c475a..09b7bbb 100644 --- a/mwparserfromhell/smart_list.py +++ b/mwparserfromhell/smart_list.py @@ -41,8 +41,23 @@ def inheritdoc(method): method.__doc__ = getattr(list, method.__name__).__doc__ return method +class _SliceNormalizerMixIn(object): + """MixIn that provides a private method to normalize slices.""" -class SmartList(list): + def _normalize_slice(self, key): + """Return a slice equivalent to the input *key*, standardized.""" + if key.start is not None: + start = (len(self) + key.start) if key.start < 0 else key.start + else: + start = 0 + if key.stop is not None: + stop = (len(self) + key.stop) if key.stop < 0 else key.stop + else: + stop = maxsize + return slice(start, stop, key.step or 1) + + +class SmartList(_SliceNormalizerMixIn, list): """Implements the ``list`` interface with special handling of sublists. When a sublist is created (by ``list[i:j]``), any changes made to this @@ -76,8 +91,8 @@ class SmartList(list): def __getitem__(self, key): if not isinstance(key, slice): return super(SmartList, self).__getitem__(key) - keystop = maxsize if key.stop is None else key.stop - sliceinfo = [key.start or 0, keystop, key.step or 1] + key = self._normalize_slice(key) + sliceinfo = [key.start, key.stop, key.step] child = _ListProxy(self, sliceinfo) self._children[id(child)] = (child, sliceinfo) return child @@ -87,9 +102,8 @@ class SmartList(list): return super(SmartList, self).__setitem__(key, item) item = list(item) super(SmartList, self).__setitem__(key, item) - keystop = maxsize if key.stop is None else key.stop - key = slice(key.start or 0, keystop, key.step or 1) - diff = len(item) + (key.start - key.stop) / key.step + key = self._normalize_slice(key) + diff = len(item) + (key.start - key.stop) // key.step values = self._children.values if py3k else self._children.itervalues if diff: for child, (start, stop, step) in values(): @@ -101,11 +115,10 @@ class SmartList(list): def __delitem__(self, key): super(SmartList, self).__delitem__(key) if isinstance(key, slice): - keystop = maxsize if key.stop is None else key.stop - key = slice(key.start or 0, keystop, key.step or 1) + key = self._normalize_slice(key) else: key = slice(key, key + 1, 1) - diff = (key.stop - key.start) / key.step + diff = (key.stop - key.start) // key.step values = self._children.values if py3k else self._children.itervalues for child, (start, stop, step) in values(): if start > key.start: @@ -166,22 +179,35 @@ class SmartList(list): child._parent = copy super(SmartList, self).reverse() - @inheritdoc - def sort(self, cmp=None, key=None, reverse=None): - copy = list(self) - for child in self._children: - child._parent = copy - kwargs = {} - if cmp is not None: - kwargs["cmp"] = cmp - if key is not None: - kwargs["key"] = key - if reverse is not None: - kwargs["reverse"] = reverse - super(SmartList, self).sort(**kwargs) - - -class _ListProxy(list): + if py3k: + @inheritdoc + def sort(self, key=None, reverse=None): + copy = list(self) + for child in self._children: + child._parent = copy + kwargs = {} + if key is not None: + kwargs["key"] = key + if reverse is not None: + kwargs["reverse"] = reverse + super(SmartList, self).sort(**kwargs) + else: + @inheritdoc + def sort(self, cmp=None, key=None, reverse=None): + copy = list(self) + for child in self._children: + child._parent = copy + kwargs = {} + if cmp is not None: + kwargs["cmp"] = cmp + if key is not None: + kwargs["key"] = key + if reverse is not None: + kwargs["reverse"] = reverse + super(SmartList, self).sort(**kwargs) + + +class _ListProxy(_SliceNormalizerMixIn, list): """Implement the ``list`` interface by getting elements from a parent. This is created by a :py:class:`~.SmartList` object when slicing. It does @@ -235,19 +261,28 @@ class _ListProxy(list): return bool(self._render()) def __len__(self): - return (self._stop - self._start) / self._step + return (self._stop - self._start) // self._step def __getitem__(self, key): - return self._render()[key] + if isinstance(key, slice): + key = self._normalize_slice(key) + if key.stop == maxsize: + keystop = self._stop + else: + keystop = key.stop + self._start + adjusted = slice(key.start + self._start, keystop, key.step) + return self._parent[adjusted] + else: + return self._render()[key] def __setitem__(self, key, item): if isinstance(key, slice): - keystart = (key.start or 0) + self._start - if key.stop is None or key.stop == maxsize: + key = self._normalize_slice(key) + if key.stop == maxsize: keystop = self._stop else: keystop = key.stop + self._start - adjusted = slice(keystart, keystop, key.step) + adjusted = slice(key.start + self._start, keystop, key.step) self._parent[adjusted] = item else: length = len(self) @@ -259,12 +294,12 @@ class _ListProxy(list): def __delitem__(self, key): if isinstance(key, slice): - keystart = (key.start or 0) + self._start - if key.stop is None or key.stop == maxsize: + key = self._normalize_slice(key) + if key.stop == maxsize: keystop = self._stop else: keystop = key.stop + self._start - adjusted = slice(keystart, keystop, key.step) + adjusted = slice(key.start + self._start, keystop, key.step) del self._parent[adjusted] else: length = len(self) @@ -388,18 +423,30 @@ class _ListProxy(list): item.reverse() self._parent[self._start:self._stop:self._step] = item - @inheritdoc - def sort(self, cmp=None, key=None, reverse=None): - item = self._render() - kwargs = {} - if cmp is not None: - kwargs["cmp"] = cmp - if key is not None: - kwargs["key"] = key - if reverse is not None: - kwargs["reverse"] = reverse - item.sort(**kwargs) - self._parent[self._start:self._stop:self._step] = item + if py3k: + @inheritdoc + def sort(self, key=None, reverse=None): + item = self._render() + kwargs = {} + if key is not None: + kwargs["key"] = key + if reverse is not None: + kwargs["reverse"] = reverse + item.sort(**kwargs) + self._parent[self._start:self._stop:self._step] = item + else: + @inheritdoc + def sort(self, cmp=None, key=None, reverse=None): + item = self._render() + kwargs = {} + if cmp is not None: + kwargs["cmp"] = cmp + if key is not None: + kwargs["key"] = key + if reverse is not None: + kwargs["reverse"] = reverse + item.sort(**kwargs) + self._parent[self._start:self._stop:self._step] = item del inheritdoc diff --git a/tests/test_smart_list.py b/tests/test_smart_list.py index 01caca7..3423bb7 100644 --- a/tests/test_smart_list.py +++ b/tests/test_smart_list.py @@ -123,7 +123,7 @@ class TestSmartList(unittest.TestCase): if py3k: self.assertEqual("[0, 1, 2, 3, 'one', 'two']", str(list1)) - self.assertEqual(b"[0, 1, 2, 3, 'one', 'two']", bytes(list1)) + self.assertEqual(b"\x00\x01\x02", bytes(list4)) self.assertEqual("[0, 1, 2, 3, 'one', 'two']", repr(list1)) else: self.assertEqual("[0, 1, 2, 3, u'one', u'two']", unicode(list1)) @@ -256,10 +256,12 @@ class TestSmartList(unittest.TestCase): self.assertEqual([0, 2, 2, 3, 4, 5], list1) list1.sort(reverse=True) self.assertEqual([5, 4, 3, 2, 2, 0], list1) - list1.sort(cmp=lambda x, y: abs(3 - x) - abs(3 - y)) # Distance from 3 - self.assertEqual([3, 4, 2, 2, 5, 0], list1) - list1.sort(cmp=lambda x, y: abs(3 - x) - abs(3 - y), reverse=True) - self.assertEqual([0, 5, 4, 2, 2, 3], list1) + if not py3k: + func = lambda x, y: abs(3 - x) - abs(3 - y) # Distance from 3 + list1.sort(cmp=func) + self.assertEqual([3, 4, 2, 2, 5, 0], list1) + list1.sort(cmp=func, reverse=True) + self.assertEqual([0, 5, 4, 2, 2, 3], list1) list3.sort(key=lambda i: i[1]) self.assertEqual([("d", 2), ("c", 3), ("a", 5), ("b", 8)], list3) list3.sort(key=lambda i: i[1], reverse=True)