move smart_list into sub-package/multiple filestags/v0.6
| @@ -1,6 +1,7 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| # | # | ||||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | ||||
| # Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
| # | # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | # of this software and associated documentation files (the "Software"), to deal | ||||
| @@ -20,201 +21,10 @@ | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| # SOFTWARE. | # SOFTWARE. | ||||
| """ | |||||
| This module contains the :class:`.SmartList` type, as well as its | |||||
| :class:`._ListProxy` child, which together implement a list whose sublists | |||||
| reflect changes made to the main list, and vice-versa. | |||||
| """ | |||||
| from __future__ import unicode_literals | |||||
| from sys import maxsize | |||||
| from weakref import ref | |||||
| from .compat import py3k | |||||
| __all__ = ["SmartList"] | |||||
| def inheritdoc(method): | |||||
| """Set __doc__ of *method* to __doc__ of *method* in its parent class. | |||||
| Since this is used on :class:`.SmartList`, the "parent class" used is | |||||
| ``list``. This function can be used as a decorator. | |||||
| """ | |||||
| method.__doc__ = getattr(list, method.__name__).__doc__ | |||||
| return method | |||||
| class _SliceNormalizerMixIn(object): | |||||
| """MixIn that provides a private method to normalize slices.""" | |||||
| def _normalize_slice(self, key, clamp=False): | |||||
| """Return a slice equivalent to the input *key*, standardized.""" | |||||
| if key.start is None: | |||||
| start = 0 | |||||
| else: | |||||
| start = (len(self) + key.start) if key.start < 0 else key.start | |||||
| if key.stop is None or key.stop == maxsize: | |||||
| stop = len(self) if clamp else None | |||||
| else: | |||||
| stop = (len(self) + key.stop) if key.stop < 0 else key.stop | |||||
| 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 | |||||
| list (such as the addition, removal, or replacement of elements) will be | |||||
| reflected in the sublist, or vice-versa, to the greatest degree possible. | |||||
| This is implemented by having sublists - instances of the | |||||
| :class:`._ListProxy` type - dynamically determine their elements by storing | |||||
| their slice info and retrieving that slice from the parent. Methods that | |||||
| change the size of the list also change the slice info. For example:: | |||||
| >>> parent = SmartList([0, 1, 2, 3]) | |||||
| >>> parent | |||||
| [0, 1, 2, 3] | |||||
| >>> child = parent[2:] | |||||
| >>> child | |||||
| [2, 3] | |||||
| >>> child.append(4) | |||||
| >>> child | |||||
| [2, 3, 4] | |||||
| >>> parent | |||||
| [0, 1, 2, 3, 4] | |||||
| """ | |||||
| def __init__(self, iterable=None): | |||||
| if iterable: | |||||
| super(SmartList, self).__init__(iterable) | |||||
| else: | |||||
| super(SmartList, self).__init__() | |||||
| self._children = {} | |||||
| def __getitem__(self, key): | |||||
| if not isinstance(key, slice): | |||||
| return super(SmartList, self).__getitem__(key) | |||||
| key = self._normalize_slice(key, clamp=False) | |||||
| sliceinfo = [key.start, key.stop, key.step] | |||||
| child = _ListProxy(self, sliceinfo) | |||||
| child_ref = ref(child, self._delete_child) | |||||
| self._children[id(child_ref)] = (child_ref, sliceinfo) | |||||
| return child | |||||
| def __setitem__(self, key, item): | |||||
| if not isinstance(key, slice): | |||||
| return super(SmartList, self).__setitem__(key, item) | |||||
| item = list(item) | |||||
| super(SmartList, self).__setitem__(key, item) | |||||
| key = self._normalize_slice(key, clamp=True) | |||||
| diff = len(item) + (key.start - key.stop) // key.step | |||||
| if not diff: | |||||
| return | |||||
| values = self._children.values if py3k else self._children.itervalues | |||||
| for child, (start, stop, step) in values(): | |||||
| if start > key.stop: | |||||
| self._children[id(child)][1][0] += diff | |||||
| if stop is not None and stop >= key.stop: | |||||
| self._children[id(child)][1][1] += diff | |||||
| def __delitem__(self, key): | |||||
| super(SmartList, self).__delitem__(key) | |||||
| if isinstance(key, slice): | |||||
| key = self._normalize_slice(key, clamp=True) | |||||
| else: | |||||
| key = slice(key, key + 1, 1) | |||||
| 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: | |||||
| self._children[id(child)][1][0] -= diff | |||||
| if stop is not None and stop >= key.stop: | |||||
| self._children[id(child)][1][1] -= diff | |||||
| if not py3k: | |||||
| def __getslice__(self, start, stop): | |||||
| return self.__getitem__(slice(start, stop)) | |||||
| def __setslice__(self, start, stop, iterable): | |||||
| self.__setitem__(slice(start, stop), iterable) | |||||
| def __delslice__(self, start, stop): | |||||
| self.__delitem__(slice(start, stop)) | |||||
| def __add__(self, other): | |||||
| return SmartList(list(self) + other) | |||||
| def __radd__(self, other): | |||||
| return SmartList(other + list(self)) | |||||
| def __iadd__(self, other): | |||||
| self.extend(other) | |||||
| 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): | |||||
| """Remove all children and give them independent parent copies.""" | |||||
| children = [val[0] for val in self._children.values()] | |||||
| for child in children: | |||||
| child()._parent = list(self) | |||||
| self._children.clear() | |||||
| @inheritdoc | |||||
| def append(self, item): | |||||
| head = len(self) | |||||
| self[head:head] = [item] | |||||
| @inheritdoc | |||||
| def extend(self, item): | |||||
| head = len(self) | |||||
| self[head:head] = item | |||||
| @inheritdoc | |||||
| def insert(self, index, item): | |||||
| self[index:index] = [item] | |||||
| @inheritdoc | |||||
| def pop(self, index=None): | |||||
| if index is None: | |||||
| index = len(self) - 1 | |||||
| item = self[index] | |||||
| del self[index] | |||||
| return item | |||||
| @inheritdoc | |||||
| def remove(self, item): | |||||
| del self[self.index(item)] | |||||
| @inheritdoc | |||||
| def reverse(self): | |||||
| self._detach_children() | |||||
| super(SmartList, self).reverse() | |||||
| if py3k: | |||||
| @inheritdoc | |||||
| def sort(self, key=None, reverse=None): | |||||
| self._detach_children() | |||||
| 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): | |||||
| self._detach_children() | |||||
| 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) | |||||
| # SmartList has to be a full import in order to avoid cyclical import errors | |||||
| import mwparserfromhell.smart_list.SmartList | |||||
| from .utils import _SliceNormalizerMixIn, inheritdoc | |||||
| from ..compat import py3k | |||||
| class _ListProxy(_SliceNormalizerMixIn, list): | class _ListProxy(_SliceNormalizerMixIn, list): | ||||
| @@ -339,20 +149,20 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||||
| self.__delitem__(slice(start, stop)) | self.__delitem__(slice(start, stop)) | ||||
| def __add__(self, other): | def __add__(self, other): | ||||
| return SmartList(list(self) + other) | |||||
| return mwparserfromhell.smart_list.SmartList(list(self) + other) | |||||
| def __radd__(self, other): | def __radd__(self, other): | ||||
| return SmartList(other + list(self)) | |||||
| return mwparserfromhell.smart_list.SmartList(other + list(self)) | |||||
| def __iadd__(self, other): | def __iadd__(self, other): | ||||
| self.extend(other) | self.extend(other) | ||||
| return self | return self | ||||
| def __mul__(self, other): | def __mul__(self, other): | ||||
| return SmartList(list(self) * other) | |||||
| return mwparserfromhell.smart_list.SmartList(list(self) * other) | |||||
| def __rmul__(self, other): | def __rmul__(self, other): | ||||
| return SmartList(other * list(self)) | |||||
| return mwparserfromhell.smart_list.SmartList(other * list(self)) | |||||
| def __imul__(self, other): | def __imul__(self, other): | ||||
| self.extend(list(self) * (other - 1)) | self.extend(list(self) * (other - 1)) | ||||
| @@ -451,6 +261,3 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||||
| kwargs["reverse"] = reverse | kwargs["reverse"] = reverse | ||||
| item.sort(**kwargs) | item.sort(**kwargs) | ||||
| self._parent[self._start:self._stop:self._step] = item | self._parent[self._start:self._stop:self._step] = item | ||||
| del inheritdoc | |||||
| @@ -0,0 +1,185 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| # Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| from _weakref import ref | |||||
| from .ListProxy import _ListProxy | |||||
| from .utils import _SliceNormalizerMixIn, inheritdoc | |||||
| from ..compat import py3k | |||||
| 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 | |||||
| list (such as the addition, removal, or replacement of elements) will be | |||||
| reflected in the sublist, or vice-versa, to the greatest degree possible. | |||||
| This is implemented by having sublists - instances of the | |||||
| :class:`._ListProxy` type - dynamically determine their elements by storing | |||||
| their slice info and retrieving that slice from the parent. Methods that | |||||
| change the size of the list also change the slice info. For example:: | |||||
| >>> parent = SmartList([0, 1, 2, 3]) | |||||
| >>> parent | |||||
| [0, 1, 2, 3] | |||||
| >>> child = parent[2:] | |||||
| >>> child | |||||
| [2, 3] | |||||
| >>> child.append(4) | |||||
| >>> child | |||||
| [2, 3, 4] | |||||
| >>> parent | |||||
| [0, 1, 2, 3, 4] | |||||
| """ | |||||
| def __init__(self, iterable=None): | |||||
| if iterable: | |||||
| super(SmartList, self).__init__(iterable) | |||||
| else: | |||||
| super(SmartList, self).__init__() | |||||
| self._children = {} | |||||
| def __getitem__(self, key): | |||||
| if not isinstance(key, slice): | |||||
| return super(SmartList, self).__getitem__(key) | |||||
| key = self._normalize_slice(key, clamp=False) | |||||
| sliceinfo = [key.start, key.stop, key.step] | |||||
| child = _ListProxy(self, sliceinfo) | |||||
| child_ref = ref(child, self._delete_child) | |||||
| self._children[id(child_ref)] = (child_ref, sliceinfo) | |||||
| return child | |||||
| def __setitem__(self, key, item): | |||||
| if not isinstance(key, slice): | |||||
| return super(SmartList, self).__setitem__(key, item) | |||||
| item = list(item) | |||||
| super(SmartList, self).__setitem__(key, item) | |||||
| key = self._normalize_slice(key, clamp=True) | |||||
| diff = len(item) + (key.start - key.stop) // key.step | |||||
| if not diff: | |||||
| return | |||||
| values = self._children.values if py3k else self._children.itervalues | |||||
| for child, (start, stop, step) in values(): | |||||
| if start > key.stop: | |||||
| self._children[id(child)][1][0] += diff | |||||
| if stop is not None and stop >= key.stop: | |||||
| self._children[id(child)][1][1] += diff | |||||
| def __delitem__(self, key): | |||||
| super(SmartList, self).__delitem__(key) | |||||
| if isinstance(key, slice): | |||||
| key = self._normalize_slice(key, clamp=True) | |||||
| else: | |||||
| key = slice(key, key + 1, 1) | |||||
| 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: | |||||
| self._children[id(child)][1][0] -= diff | |||||
| if stop is not None and stop >= key.stop: | |||||
| self._children[id(child)][1][1] -= diff | |||||
| if not py3k: | |||||
| def __getslice__(self, start, stop): | |||||
| return self.__getitem__(slice(start, stop)) | |||||
| def __setslice__(self, start, stop, iterable): | |||||
| self.__setitem__(slice(start, stop), iterable) | |||||
| def __delslice__(self, start, stop): | |||||
| self.__delitem__(slice(start, stop)) | |||||
| def __add__(self, other): | |||||
| return SmartList(list(self) + other) | |||||
| def __radd__(self, other): | |||||
| return SmartList(other + list(self)) | |||||
| def __iadd__(self, other): | |||||
| self.extend(other) | |||||
| 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): | |||||
| """Remove all children and give them independent parent copies.""" | |||||
| children = [val[0] for val in self._children.values()] | |||||
| for child in children: | |||||
| child()._parent = list(self) | |||||
| self._children.clear() | |||||
| @inheritdoc | |||||
| def append(self, item): | |||||
| head = len(self) | |||||
| self[head:head] = [item] | |||||
| @inheritdoc | |||||
| def extend(self, item): | |||||
| head = len(self) | |||||
| self[head:head] = item | |||||
| @inheritdoc | |||||
| def insert(self, index, item): | |||||
| self[index:index] = [item] | |||||
| @inheritdoc | |||||
| def pop(self, index=None): | |||||
| if index is None: | |||||
| index = len(self) - 1 | |||||
| item = self[index] | |||||
| del self[index] | |||||
| return item | |||||
| @inheritdoc | |||||
| def remove(self, item): | |||||
| del self[self.index(item)] | |||||
| @inheritdoc | |||||
| def reverse(self): | |||||
| self._detach_children() | |||||
| super(SmartList, self).reverse() | |||||
| if py3k: | |||||
| @inheritdoc | |||||
| def sort(self, key=None, reverse=None): | |||||
| self._detach_children() | |||||
| 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): | |||||
| self._detach_children() | |||||
| 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) | |||||
| @@ -0,0 +1,30 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| # Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| """ | |||||
| This module contains the :class:`.SmartList` type, as well as its | |||||
| :class:`._ListProxy` child, which together implement a list whose sublists | |||||
| reflect changes made to the main list, and vice-versa. | |||||
| """ | |||||
| from .SmartList import SmartList | |||||
| @@ -0,0 +1,54 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| # Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| from __future__ import unicode_literals | |||||
| from sys import maxsize | |||||
| __all__ = [] | |||||
| def inheritdoc(method): | |||||
| """Set __doc__ of *method* to __doc__ of *method* in its parent class. | |||||
| Since this is used on :class:`.SmartList`, the "parent class" used is | |||||
| ``list``. This function can be used as a decorator. | |||||
| """ | |||||
| method.__doc__ = getattr(list, method.__name__).__doc__ | |||||
| return method | |||||
| class _SliceNormalizerMixIn(object): | |||||
| """MixIn that provides a private method to normalize slices.""" | |||||
| def _normalize_slice(self, key, clamp=False): | |||||
| """Return a slice equivalent to the input *key*, standardized.""" | |||||
| if key.start is None: | |||||
| start = 0 | |||||
| else: | |||||
| start = (len(self) + key.start) if key.start < 0 else key.start | |||||
| if key.stop is None or key.stop == maxsize: | |||||
| stop = len(self) if clamp else None | |||||
| else: | |||||
| stop = (len(self) + key.stop) if key.stop < 0 else key.stop | |||||
| return slice(start, stop, key.step or 1) | |||||
| @@ -21,13 +21,14 @@ | |||||
| # SOFTWARE. | # SOFTWARE. | ||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
| from itertools import chain | |||||
| import re | import re | ||||
| from itertools import chain | |||||
| from .compat import bytes, py3k, range, str | from .compat import bytes, py3k, range, str | ||||
| from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | ||||
| Node, Tag, Template, Text, Wikilink) | Node, Tag, Template, Text, Wikilink) | ||||
| from .smart_list import _ListProxy | |||||
| from .smart_list.ListProxy import _ListProxy | |||||
| from .string_mixin import StringMixIn | from .string_mixin import StringMixIn | ||||
| from .utils import parse_anything | from .utils import parse_anything | ||||
| @@ -21,10 +21,13 @@ | |||||
| # SOFTWARE. | # SOFTWARE. | ||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
| import unittest | import unittest | ||||
| from mwparserfromhell.compat import py3k, range | from mwparserfromhell.compat import py3k, range | ||||
| from mwparserfromhell.smart_list import SmartList, _ListProxy | |||||
| from mwparserfromhell.smart_list import SmartList | |||||
| from mwparserfromhell.smart_list.ListProxy import _ListProxy | |||||
| class TestSmartList(unittest.TestCase): | class TestSmartList(unittest.TestCase): | ||||
| """Test cases for the SmartList class and its child, _ListProxy.""" | """Test cases for the SmartList class and its child, _ListProxy.""" | ||||