To support (coding and) code review (especially when asking for alternatives), state (in the code) the goal pursued during coding. (Double linkage/
last serves no purpose I can see.)
If not literally re-inventing a support on solid surfaces combining low longitudinal friction with good lateral guide, use/implement an existing "protocol"/"interface" - mutable sequence comes to mind seeing the methods you present; something like test_MutableSequence() may fall into your lap.
Explicitly specify everything protocol but not standard using docstrings. Sketch tests: If you don't know what to test, you don't know what to implement.
- specify what is to happen with parameter values without "natural" meaning, e.g., index smaller zero or not smaller
- (as @Peilonrayz demanded:) Don’t Repeat Yourself
- provide docstrings for classes (and modules), too
- check comments and, arguably more important, docstrings for correctness - when a second error passes unit testing after thinking unit tests complete, revise unit tests
Observations about the code,
[especially] insert, find, replace, at_index:
insert fails to update
_find) might use
quality satisfied with more than one node's
data, both are underspecified.
count, walk backwards
(it may be useful to allow indices from
-count (even with
at_index or generally) - cf. slicing)
delete looks non-adapted from a singly-linked list implementation (no need for
current.next.last should be set depending on
insert fails to set
reverse: how about transmuting to an instance of
ListLinkedDoubly, with roles of
tail, if sticking with no sentinel node (@Peilonrayz, again))
reverse() is a costly operation -
reversed() returns an iterator
return '' if 0 == self.count \
else '[-(' + ')<=>('.join(self.items()) + ')-]'
Trying to stay DRY implementing
def _add(self, item): """Update count and return new Node, attached to list if empty before. """ self.count += 1 new_node = Node(item) if self.head is None: self.head = self.tail = new_node return new_node def append(self, item): """Return self with item inserted at the tail.""" new_node = self._add(item) if 1 < self.count: self.tail.next = new_node new_node.last = self.tail self.tail = new_node def prepend(self, item): """Return self with item inserted at the head.""" new_node = self._add(item) if 1 < self.count: self.head.last = new_node new_node.next = self.head self.head = new_node
(I "real life", I like to be able to chain calls: