Skip to content

Commit 1012001

Browse files
committed
Added reimplementation of collections.UserList, and use that as the base for NamedList.
1 parent c347ff7 commit 1012001

File tree

7 files changed

+1612
-102
lines changed

7 files changed

+1612
-102
lines changed

doc-source/api/bases.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ Dictable
1414
:special-members:
1515

1616

17+
UserList
18+
---------
19+
20+
.. autoclass:: domdf_python_tools.bases.UserList
21+
:inherited-members:
22+
:special-members:
23+
24+
1725
NamedList
1826
----------
1927

@@ -48,7 +56,5 @@ and not this:
4856
This avoids any potential issues with `mypy <http://mypy-lang.org/>`_
4957
5058
.. autoclass:: domdf_python_tools.bases.NamedList
51-
:inherited-members:
52-
:special-members:
5359

5460
.. autofunction:: domdf_python_tools.bases.namedlist

domdf_python_tools/bases.py

Lines changed: 278 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,39 @@
2222
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
2323
# MA 02110-1301, USA.
2424
#
25+
# UserList based on CPython.
26+
# Licensed under the Python Software Foundation License Version 2.
27+
# Copyright © 2001-2020 Python Software Foundation. All rights reserved.
28+
# Copyright © 2000 BeOpen.com . All rights reserved.
29+
# Copyright © 1995-2000 Corporation for National Research Initiatives . All rights reserved.
30+
# Copyright © 1991-1995 Stichting Mathematisch Centrum . All rights reserved.
31+
#
2532

2633
# stdlib
2734
from abc import abstractmethod
28-
from collections import UserList
2935
from pprint import pformat
30-
from typing import Any, Dict, Iterable, Tuple, Type
36+
from typing import (
37+
Any,
38+
Dict,
39+
Iterable,
40+
Iterator,
41+
List,
42+
MutableSequence,
43+
Optional,
44+
Tuple,
45+
Type,
46+
TypeVar,
47+
Union,
48+
overload
49+
)
3150

3251
# 3rd party
3352
import pydash # type: ignore
3453

35-
__all__ = ["Dictable", "NamedList", "namedlist"]
54+
__all__ = ["Dictable", "NamedList", "namedlist", "UserList"]
55+
56+
# this package
57+
from domdf_python_tools.doctools import prettify_docstrings
3658

3759

3860
class Dictable(Iterable):
@@ -74,11 +96,263 @@ def __eq__(self, other) -> bool:
7496
return NotImplemented
7597

7698

77-
class NamedList(UserList):
99+
_T = TypeVar("_T")
100+
_S = TypeVar('_S')
101+
102+
103+
@prettify_docstrings
104+
class UserList(MutableSequence[_T]):
105+
"""
106+
Typed version of :class:`collections.UserList`.
107+
108+
Class that simulates a list. The instance’s contents are kept in a regular list,
109+
which is accessible via the :attr:`~.UserList.data` attribute of UserList instances.
110+
The instance’s contents are initially set to a copy of list, defaulting to the
111+
empty list ``[]``.
112+
113+
:param initlist: Values to initialise the :class:`~domdf_python_tools.bases.UserList` with.
114+
:default initlist: ``[]``
115+
116+
117+
.. admonition:: Subclassing requirements
118+
119+
Subclasses of UserList are expected to offer a constructor which can be called with
120+
either no arguments or one argument. List operations which return a new sequence
121+
attempt to create an instance of the actual implementation class. To do so,
122+
it assumes that the constructor can be called with a single parameter, which is a
123+
sequence object used as a data source.
124+
125+
If a derived class does not wish to comply with this requirement, all of the special
126+
methods supported by this class will need to be overridden; please consult the
127+
sources for information about the methods which need to be provided in that case.
128+
129+
.. versionadded:: 1.0.0
130+
"""
131+
132+
#: A real list object used to store the contents of the :class:`~domdf_python_tools.bases.UserList`.
133+
data: List[_T]
134+
135+
def __init__(self, initlist: Optional[Iterable[_T]] = None):
136+
self.data = []
137+
if initlist is not None:
138+
# XXX should this accept an arbitrary sequence?
139+
if type(initlist) is type(self.data): # noqa E721
140+
self.data[:] = initlist
141+
elif isinstance(initlist, UserList):
142+
self.data[:] = initlist.data[:]
143+
else:
144+
self.data = list(initlist)
145+
146+
def __repr__(self) -> str:
147+
return repr(self.data)
148+
149+
def __lt__(self, other: object) -> bool:
150+
return self.data < self.__cast(other)
151+
152+
def __le__(self, other: object) -> bool:
153+
return self.data <= self.__cast(other)
154+
155+
def __eq__(self, other: object) -> bool:
156+
return self.data == self.__cast(other)
157+
158+
def __gt__(self, other: object) -> bool:
159+
return self.data > self.__cast(other)
160+
161+
def __ge__(self, other: object) -> bool:
162+
return self.data >= self.__cast(other)
163+
164+
@staticmethod
165+
def __cast(other):
166+
return other.data if isinstance(other, UserList) else other
167+
168+
def __contains__(self, item: object) -> bool:
169+
return item in self.data
170+
171+
def __len__(self) -> int:
172+
return len(self.data)
173+
174+
def __iter__(self) -> Iterator[_T]:
175+
yield from self.data
176+
177+
@overload
178+
def __getitem__(self, i: int) -> _T:
179+
... # pragma: no cover
180+
181+
@overload
182+
def __getitem__(self, i: slice) -> MutableSequence[_T]:
183+
... # pragma: no cover
184+
185+
def __getitem__(self, i: Union[int, slice]) -> Union[_T, MutableSequence[_T]]:
186+
if isinstance(i, slice):
187+
return self.__class__(self.data[i])
188+
else:
189+
return self.data[i]
190+
191+
@overload
192+
def __setitem__(self, i: int, o: _T) -> None:
193+
... # pragma: no cover
194+
195+
@overload
196+
def __setitem__(self, i: slice, o: Iterable[_T]) -> None:
197+
... # pragma: no cover
198+
199+
def __setitem__(self, i: Union[int, slice], item: Union[_T, Iterable[_T]]) -> None:
200+
self.data[i] = item
201+
202+
def __delitem__(self, i: Union[int, slice]):
203+
del self.data[i]
204+
205+
def __add__(self: _S, other: Iterable[_T]) -> _S:
206+
if isinstance(other, UserList):
207+
return self.__class__(self.data + other.data)
208+
elif isinstance(other, type(self.data)):
209+
return self.__class__(self.data + other)
210+
return self.__class__(self.data + list(other))
211+
212+
def __radd__(self, other):
213+
if isinstance(other, UserList):
214+
return self.__class__(other.data + self.data)
215+
elif isinstance(other, type(self.data)):
216+
return self.__class__(other + self.data)
217+
return self.__class__(list(other) + self.data)
218+
219+
def __iadd__(self: _S, other: Iterable[_T]) -> _S:
220+
if isinstance(other, UserList):
221+
self.data += other.data
222+
elif isinstance(other, type(self.data)):
223+
self.data += other
224+
else:
225+
self.data += list(other)
226+
return self
227+
228+
def __mul__(self: _S, n: int) -> _S:
229+
return self.__class__(self.data * n)
230+
231+
__rmul__ = __mul__
232+
233+
def __imul__(self: _S, n: int) -> _S:
234+
self.data *= n
235+
return self
236+
237+
def __copy__(self):
238+
inst = self.__class__.__new__(self.__class__)
239+
inst.__dict__.update(self.__dict__)
240+
# Create a copy and avoid triggering descriptors
241+
inst.__dict__["data"] = self.__dict__["data"][:]
242+
return inst
243+
244+
def append(self, item: _T) -> None:
245+
"""
246+
Append ``item`` to the end of the :class:`~.domdf_python_tools.bases.UserList`.
247+
"""
248+
249+
self.data.append(item)
250+
251+
def insert(self, i: int, item: _T) -> None:
252+
"""
253+
Insert ``item`` at position ``i`` in the :class:`~.domdf_python_tools.bases.UserList`.
254+
"""
255+
256+
self.data.insert(i, item)
257+
258+
def pop(self, i: int = -1) -> _T:
259+
"""
260+
Removes and returns the item at index ``i``.
261+
262+
:raises IndexError: if list is empty or index is out of range.
263+
"""
264+
265+
return self.data.pop(i)
266+
267+
def remove(self, item: _T) -> None:
268+
"""
269+
Removes the first occurrence of ``item`` from the list.
270+
271+
:param item:
272+
273+
:raises ValueError: if the item is not present.
274+
"""
275+
276+
self.data.remove(item)
277+
278+
def clear(self) -> None:
279+
"""
280+
Remove all items from the :class:`~.domdf_python_tools.bases.UserList`.
281+
"""
282+
283+
self.data.clear()
284+
285+
def copy(self: _S) -> _S:
286+
"""
287+
Returns a copy of the :class:`~.domdf_python_tools.bases.UserList`.
288+
"""
289+
290+
return self.__class__(self)
291+
292+
def count(self, item: _T) -> int:
293+
"""
294+
Returns the number of occurrences of ``item`` in the :class:`~.domdf_python_tools.bases.UserList`.
295+
"""
296+
297+
return self.data.count(item)
298+
299+
def index(self, item: _T, *args: Any) -> int:
300+
"""
301+
Returns the index of the fist element matching ``item``.
302+
303+
:param item:
304+
:param args:
305+
306+
:raises ValueError: if the item is not present.
307+
"""
308+
309+
return self.data.index(item, *args)
310+
311+
def reverse(self) -> None:
312+
"""
313+
Reverse the list in place.
314+
"""
315+
316+
self.data.reverse()
317+
318+
def sort(self, *, key=None, reverse: bool = False) -> None:
319+
"""
320+
Sort the list in ascending order and return :py:obj:`None`.
321+
322+
The sort is in-place (i.e. the list itself is modified) and stable (i.e. the
323+
order of two equal elements is maintained).
324+
325+
If a key function is given, apply it once to each list item and sort them,
326+
ascending or descending, according to their function values.
327+
328+
The reverse flag can be set to sort in descending order.
329+
"""
330+
331+
self.data.sort(key=key, reverse=reverse)
332+
333+
def extend(self, other: Iterable[_T]) -> None:
334+
"""
335+
Extend the :class:`~.domdf_python_tools.bases.NamedList` by appending elements from ``other``.
336+
337+
:param other:
338+
"""
339+
340+
if isinstance(other, UserList):
341+
self.data.extend(other.data)
342+
else:
343+
self.data.extend(other)
344+
345+
346+
@prettify_docstrings
347+
class NamedList(UserList[_T]):
78348
"""
79349
A list with a name.
80350
81351
The name of the list is taken from the name of the subclass.
352+
353+
.. versionchanged:: 1.0.0
354+
355+
:class:`~.NamedList` now subclasses :class:`.UserList` rather than :class:`collections.UserList`.
82356
"""
83357

84358
def __repr__(self) -> str:

0 commit comments

Comments
 (0)