Implementing Linked lists in Python from scratch
Review

You should take advantage of the bi-directional nature of a doubly linked list. It's a pitty to let it use navigation of a normal linked list just for the sake of enabling inheritance. And why should a normal linked list be able to branch between normal and doubly mode? This is a code smell.

A practical way to create a doubly linked list, is to create a circular list. You only need to store the head. tail would be head.prev. This also works with a single node head = head.prev = head.next. The advantage is that less if statements are required to perform insert / delete operations. Walking the nodes starts at head and stops until we encounter head again. We could walk both directions if we wish to.

You can initialise a single node:

self.head = ListNode(x, doubly)
self.head.prev = self.head
self.head.next = self.head


The delete would be significantly simplified:

After walking the nodes until you get the proper node p given x, you can call

if p.next == p:
    self.head = None
else:
    p.next.prev = p.prev
    p.prev.next = p.next
    if self.head == p:
        self.head = p.next
del p
self._length -= 1


I don't see a value in using inheritance for these lists.



I also don't get why you store p = self.head in insert. It's an unused variable.