| @@ -8,6 +8,8 @@ v0.4.1 (unreleased): | |||||
| includes when denoting tags, but not comments. | includes when denoting tags, but not comments. | ||||
| - Fixed the behavior of preserve_spacing in Template.add() and keep_field in | - Fixed the behavior of preserve_spacing in Template.add() and keep_field in | ||||
| Template.remove() on parameters with hidden keys. | Template.remove() on parameters with hidden keys. | ||||
| - Removed _ListProxy.detach(). SmartLists now use weak references and their | |||||
| children are garbage-collected properly. | |||||
| - Fixed parser bugs involving: | - Fixed parser bugs involving: | ||||
| - templates with completely blank names; | - templates with completely blank names; | ||||
| - templates with newlines and comments. | - templates with newlines and comments. | ||||
| @@ -13,8 +13,10 @@ Unreleased | |||||
| - Added support for Python 3.5. | - Added support for Python 3.5. | ||||
| - ``<`` and ``>`` are now disallowed in wikilink titles and template names. | - ``<`` and ``>`` are now disallowed in wikilink titles and template names. | ||||
| This includes when denoting tags, but not comments. | This includes when denoting tags, but not comments. | ||||
| - Fixed the behavior of *preserve_spacing* in :func:`~.Template.add` and | |||||
| *keep_field* in :func:`~.Template.remove` on parameters with hidden keys. | |||||
| - Fixed the behavior of *preserve_spacing* in :meth:`.Template.add` and | |||||
| *keep_field* in :meth:`.Template.remove` on parameters with hidden keys. | |||||
| - Removed :meth:`._ListProxy.detach`. :class:`.SmartList`\ s now use weak | |||||
| references and their children are garbage-collected properly. | |||||
| - Fixed parser bugs involving: | - Fixed parser bugs involving: | ||||
| - templates with completely blank names; | - templates with completely blank names; | ||||
| @@ -27,6 +27,7 @@ reflect changes made to the main list, and vice-versa. | |||||
| """ | """ | ||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
| from weakref import ref | |||||
| from .compat import maxsize, py3k | from .compat import maxsize, py3k | ||||
| @@ -80,13 +81,6 @@ class SmartList(_SliceNormalizerMixIn, list): | |||||
| [2, 3, 4] | [2, 3, 4] | ||||
| >>> parent | >>> parent | ||||
| [0, 1, 2, 3, 4] | [0, 1, 2, 3, 4] | ||||
| The parent needs to keep a list of its children in order to update them, | |||||
| which prevents them from being garbage-collected. If you are keeping the | |||||
| parent around for a while but creating many children, it is advisable to | |||||
| call :meth:`._ListProxy.detach` when you're finished with them. Certain | |||||
| parent methods, like :meth:`reverse` and :meth:`sort`, will do this | |||||
| automatically. | |||||
| """ | """ | ||||
| def __init__(self, iterable=None): | def __init__(self, iterable=None): | ||||
| @@ -102,7 +96,8 @@ class SmartList(_SliceNormalizerMixIn, list): | |||||
| key = self._normalize_slice(key) | key = self._normalize_slice(key) | ||||
| sliceinfo = [key.start, key.stop, key.step] | sliceinfo = [key.start, key.stop, key.step] | ||||
| child = _ListProxy(self, sliceinfo) | child = _ListProxy(self, sliceinfo) | ||||
| self._children[id(child)] = (child, sliceinfo) | |||||
| child_ref = ref(child, self._delete_child) | |||||
| self._children[id(child_ref)] = (child_ref, sliceinfo) | |||||
| return child | return child | ||||
| def __setitem__(self, key, item): | def __setitem__(self, key, item): | ||||
| @@ -112,13 +107,14 @@ class SmartList(_SliceNormalizerMixIn, list): | |||||
| super(SmartList, self).__setitem__(key, item) | super(SmartList, self).__setitem__(key, item) | ||||
| key = self._normalize_slice(key) | key = self._normalize_slice(key) | ||||
| diff = len(item) + (key.start - key.stop) // key.step | diff = len(item) + (key.start - key.stop) // key.step | ||||
| if not diff: | |||||
| return | |||||
| values = self._children.values if py3k else self._children.itervalues | values = self._children.values if py3k else self._children.itervalues | ||||
| if diff: | |||||
| for child, (start, stop, step) in values(): | |||||
| if start > key.stop: | |||||
| self._children[id(child)][1][0] += diff | |||||
| if stop >= key.stop and stop != maxsize: | |||||
| self._children[id(child)][1][1] += diff | |||||
| for child, (start, stop, step) in values(): | |||||
| if start > key.stop: | |||||
| self._children[id(child)][1][0] += diff | |||||
| if stop >= key.stop and stop != maxsize: | |||||
| self._children[id(child)][1][1] += diff | |||||
| def __delitem__(self, key): | def __delitem__(self, key): | ||||
| super(SmartList, self).__delitem__(key) | super(SmartList, self).__delitem__(key) | ||||
| @@ -154,10 +150,16 @@ class SmartList(_SliceNormalizerMixIn, list): | |||||
| self.extend(other) | self.extend(other) | ||||
| return self | return self | ||||
| def _delete_child(self, child_ref): | |||||
| """Remove a child reference that is about to be garbage-collected.""" | |||||
| del self._children[id(child_ref)] | |||||
| def _detach_children(self): | def _detach_children(self): | ||||
| """Remove all children and give them independent parent copies.""" | |||||
| children = [val[0] for val in self._children.values()] | children = [val[0] for val in self._children.values()] | ||||
| for child in children: | for child in children: | ||||
| child.detach() | |||||
| child()._parent = list(self) | |||||
| self._children.clear() | |||||
| @inheritdoc | @inheritdoc | ||||
| def append(self, item): | def append(self, item): | ||||
| @@ -226,7 +228,6 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||||
| super(_ListProxy, self).__init__() | super(_ListProxy, self).__init__() | ||||
| self._parent = parent | self._parent = parent | ||||
| self._sliceinfo = sliceinfo | self._sliceinfo = sliceinfo | ||||
| self._detached = False | |||||
| def __repr__(self): | def __repr__(self): | ||||
| return repr(self._render()) | return repr(self._render()) | ||||
| @@ -456,17 +457,5 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||||
| item.sort(**kwargs) | item.sort(**kwargs) | ||||
| self._parent[self._start:self._stop:self._step] = item | self._parent[self._start:self._stop:self._step] = item | ||||
| def detach(self): | |||||
| """Detach the child so it operates like a normal list. | |||||
| This allows children to be properly garbage-collected if their parent | |||||
| is being kept around for a long time. This method has no effect if the | |||||
| child is already detached. | |||||
| """ | |||||
| if not self._detached: | |||||
| self._parent._children.pop(id(self)) | |||||
| self._parent = list(self._parent) | |||||
| self._detached = True | |||||
| del inheritdoc | del inheritdoc | ||||
| @@ -389,28 +389,20 @@ class TestSmartList(unittest.TestCase): | |||||
| self.assertEqual([4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], child1) | self.assertEqual([4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], child1) | ||||
| self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | ||||
| child1.detach() | |||||
| del child1 | |||||
| self.assertEqual([1, 4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], parent) | self.assertEqual([1, 4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], parent) | ||||
| self.assertEqual([4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], child1) | |||||
| self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | ||||
| self.assertEqual(1, len(parent._children)) | self.assertEqual(1, len(parent._children)) | ||||
| parent.remove(1.9) | parent.remove(1.9) | ||||
| parent.remove(1.8) | parent.remove(1.8) | ||||
| self.assertEqual([1, 4, 3, 2, 5, 6, 7, 8, 8.1, 8.2], parent) | self.assertEqual([1, 4, 3, 2, 5, 6, 7, 8, 8.1, 8.2], parent) | ||||
| self.assertEqual([4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], child1) | |||||
| self.assertEqual([4, 3, 2], child2) | self.assertEqual([4, 3, 2], child2) | ||||
| parent.reverse() | parent.reverse() | ||||
| self.assertEqual([8.2, 8.1, 8, 7, 6, 5, 2, 3, 4, 1], parent) | self.assertEqual([8.2, 8.1, 8, 7, 6, 5, 2, 3, 4, 1], parent) | ||||
| self.assertEqual([4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], child1) | |||||
| self.assertEqual([4, 3, 2], child2) | self.assertEqual([4, 3, 2], child2) | ||||
| self.assertEqual(0, len(parent._children)) | self.assertEqual(0, len(parent._children)) | ||||
| child2.detach() | |||||
| self.assertEqual([8.2, 8.1, 8, 7, 6, 5, 2, 3, 4, 1], parent) | |||||
| self.assertEqual([4, 3, 2, 1.9, 1.8, 5, 6, 7, 8, 8.1, 8.2], child1) | |||||
| self.assertEqual([4, 3, 2], child2) | |||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||
| unittest.main(verbosity=2) | unittest.main(verbosity=2) | ||||