@@ -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) |