"""
    https://stepik.org/lesson/701988/step/7?unit=702089

    Объявите класс LinkedList (связный список) для работы со следующей структурой данных:

Здесь создается список из связанных между собой объектов класса ObjList. Объекты этого класса создаются командой:

obj = ObjList(data)
где data - строка с некоторой информацией. Также в каждом объекте obj класса ObjList должны создаваться следующие локальные атрибуты:

__data - ссылка на строку с данными;
__prev - ссылка на предыдущий объект связного списка (если объекта нет, то __prev = None);
__next - ссылка на следующий объект связного списка (если объекта нет, то __next = None).

В свою очередь, объекты класса LinkedList должны создаваться командой:

linked_lst = LinkedList()
и содержать локальные атрибуты:

head - ссылка на первый объект связного списка (если список пуст, то head = None);
tail - ссылка на последний объект связного списка (если список пуст, то tail = None).

А сам класс содержать следующие методы:

add_obj(obj) - добавление нового объекта obj класса ObjList в конец связного списка;
remove_obj(indx) - удаление объекта класса ObjList из связного списка по его порядковому номеру (индексу); индекс отсчитывается с нуля.

Также с объектами класса LinkedList должны поддерживаться следующие операции:

len(linked_lst) - возвращает число объектов в связном списке;
linked_lst(indx) - возвращает строку __data, хранящуюся в объекте класса ObjList, расположенного под индексом indx (в связном списке).

Пример использования классов (эти строчки в программе писать не нужно):

linked_lst = LinkedList()
linked_lst.add_obj(ObjList("Sergey"))
linked_lst.add_obj(ObjList("Balakirev"))
linked_lst.add_obj(ObjList("Python"))
linked_lst.remove_obj(2)
linked_lst.add_obj(ObjList("Python ООП"))
n = len(linked_lst)  # n = 3
s = linked_lst(1) # s = Balakirev
P.S. На экран в программе ничего выводить не нужно.     
"""


class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    def add_obj(self, obj):
        if not self.head:
            self.head = obj
            self.tail = obj
        else:
            self.tail.next = obj
            obj.prev = self.tail
            self.tail = obj

    def __iter__(self):
        obj = self.head
        while obj and obj != self.tail.next:
            yield obj
            obj = obj.next

    def __reversed__(self):
        obj = self.tail
        while obj and obj != self.head.prev:
            yield obj
            obj = obj.prev

    def remove_obj(self, idx):
        if not self:
            pass
        elif idx == 0:
            self.head = self.head.next
            if self.head:
                self.head.prev = None
        elif idx == len(self) - 1:
            self.tail = self.tail.prev
            self.tail.next = None
        else:
            obj = self.head
            for _ in range(idx):
                obj = obj.next
            obj.prev.next = obj.next
            obj.next.prev = obj.prev
        
        if not self:
            self.head = self.tail = None

    def __len__(self):
        return sum(1 for _ in self)

    def __getitem__(self, idx):
        return next(filter(lambda x: x[0] == idx, enumerate(self)))[1]

    def __call__(self, key):
        return self[key].data

    def get_data(self):
        return [*map(ObjList.get_data, self)]


class ObjList:
    def __init__(self, data=None):
        self.__next = None
        self.__prev = None
        self.__data = data

    def set_next(self, obj):
        self.__next = obj

    def set_prev(self, obj):
        self.__prev = obj

    def get_next(self):
        return self.__next

    def get_prev(self):
        return self.__prev

    def set_data(self, data):
        self.__data = data

    def get_data(self):
        return self.__data

    data = property(get_data, set_data)
    next = property(get_next, set_next)
    prev = property(get_prev, set_prev)


def tests():
    ln = LinkedList()
    ln.add_obj(ObjList("Сергей"))
    ln.add_obj(ObjList("Балакирев"))
    ln.add_obj(ObjList("Python ООП"))
    ln.remove_obj(2)
    assert (
        len(ln) == 2
    ), "функция len вернула неверное число объектов в списке, возможно, неверно работает метод remove_obj()"
    ln.add_obj(ObjList("Python"))
    assert ln(2) == "Python", "неверное значение атрибута __data, взятое по индексу"
    assert len(ln) == 3, "функция len вернула неверное число объектов в списке"
    assert ln(1) == "Балакирев", "неверное значение атрибута __data, взятое по индексу"

    n = 0
    h = ln.head
    while h:
        assert isinstance(h, ObjList)
        h = h._ObjList__next
        n += 1

    assert n == 3, "при перемещении по списку через __next не все объекты перебрались"

    n = 0
    h = ln.tail
    while h:
        assert isinstance(h, ObjList)
        h = h._ObjList__prev
        n += 1

    assert n == 3, "при перемещении по списку через __prev не все объекты перебрались"


if __name__ == "__main__":
    import doctest

    doctest.testmod()
    tests()