Type hinting subclass which contains references to other instances of its own type

Viewed 44

I am trying to create a base class for a 2 dimensional linked list. The code below is already working fine from a functionality point of view. The problem is, when I subclass LinkedList2D to create a more specific version all my attributes (up, down, before, after) and my generator functions still resolve the type hints to LinkedList2D and not to the more specific subclass, which is necessary because I want to access the additional attributes and methods of it.

I have a feeling, that I need to use bound generics somehow but I can't seem to wrap my head around it.

class LinkedList2D:
    up: Optional[LinkedList2D]
    down: Optional[LinkedList2D]
    before: Optional[LinkedList2D]
    after: Optional[LinkedList2D]

    def __init__(self) -> None:
        self.up = None
        self.down = None
        self.before = None
        self.after = None

    def iterate_children(self) -> Generator[LinkedList2D, None, None]:
        link = self.down

        while link:
            yield link

            link = link.after

    def iterate_tree(self) -> Generator[LinkedList2D, None, None]:
        for child in self.iterate_children():
            if child:
                yield child

                yield from child.iterate_children()

    def get_children(self) -> List[LinkedList2D]:
        return list(self.iterate_children())

    def insert_under(self, op: LinkedList2D) -> bool:
        if op:
            if op.down:
                self.insert_before(op.down)

            op.down = self

        self.up = op

        return True

    def insert_under_last(self, op: LinkedList2D) -> bool:
        self.up = op

        if op:
            if op.down:
                last = self.get_children()[-1]

                self.insert_after(last)
            else:
                op.down = self

        return True

    def insert_after(self, obj: LinkedList2D) -> bool:
        if obj:
            if obj.after:
                obj.after.before = self

                obj.after = self

            self.after = obj.after

            self.up = obj.up

        self.before = obj

        return True

    def insert_before(self, obj: LinkedList2D) -> bool:
        if obj:
            if obj.before:
                obj.before.after = self

            obj.before = self

            self.up = obj.up

            self.before = obj.before

        self.after = obj

        return True

The generic version would look like this, I think. I used bound to make sure the potential instances are subclasses of LinkedList2D and implement the necessary functions and attributes.

T = TypeVar("T", bound="LinkedList2D")


class LinkedList2D(Generic[T]):
    up: Optional[T]
    down: Optional[T]
    before: Optional[T]
    after: Optional[T]

    def __init__(self) -> None:
        ...

    def iterate_children(self) -> Generator[T, None, None]:
        ...

    def iterate_tree(self) -> Generator[T, None, None]:
        ...

    def get_children(self) -> List[T]:
        ...

    def insert_under(self, op: T) -> bool:
        if op:
            if op.down:
                self.insert_before(op.down)

            op.down = self

        self.up = op

        return True

    def insert_under_last(self, op: T) -> bool:
        ...

    def insert_before(self, obj: T) -> bool:
        ...

The Problem is, that now pylance is complaining about the line op.down = self in the insert_under method:

Cannot assign member "down" for type "LinkedList2D[T@LinkedList2D]"
  Expression of type "LinkedList2D[T@LinkedList2D]" cannot be assigned to member "down" of class "LinkedList2D[T@LinkedList2D]"
    Type "LinkedList2D[T@LinkedList2D]" cannot be assigned to type "T@LinkedList2D | None"
      Type "LinkedList2D[T@LinkedList2D]" cannot be assigned to type "T@LinkedList2D"
      Cannot assign to "None"PylancereportGeneralTypeIssues

Any ideas and hints are greatly appreciated. Thanks

1 Answers

So apparently it is possible to type hint self with a bound TypeVar which makes it possible for the subclasses of the generic LinkedList2D to return the correctly hinted types for get_children, iterate_children and iterate_tree. The insert_under method will also not complain anymore since it is accepting LinkedList2D instances and any subclass of it.

from __future__ import annotations

from typing import Optional, Generator, List, TypeVar

T = TypeVar("T", bound="LinkedList2D")


class LinkedList2D:
    up: Optional[LinkedList2D]
    down: Optional[LinkedList2D]
    before: Optional[LinkedList2D]
    after: Optional[LinkedList2D]

    def __init__(self) -> None:
        self.up = None
        self.down = None
        self.before = None
        self.after = None

    def iterate_children(self: T) -> Generator[T, None, None]:
        link = self.down

        while link:
            yield link

            link = link.after

    def iterate_tree(self: T) -> Generator[T, None, None]:
        for child in self.iterate_children():
            if child:
                yield child

                yield from child.iterate_children()

    def get_children(self: T) -> List[T]:
        return list(self.iterate_children())

    def insert_under(self, op: LinkedList2D) -> bool:
        if op:
            if op.down:
                self.insert_before(op.down)

            op.down = self

        self.up = op

        return True

    def insert_under_last(self, op: LinkedList2D) -> bool:
        self.up = op

        if op:
            if op.down:
                last = self.get_children()[-1]

                self.insert_after(last)
            else:
                op.down = self

        return True

    def insert_after(self, obj: LinkedList2D) -> bool:
        if obj:
            if obj.after:
                obj.after.before = self

                obj.after = self

            self.after = obj.after

            self.up = obj.up

        self.before = obj

        return True

    def insert_before(self, obj: LinkedList2D) -> bool:
        if obj:
            if obj.before:
                obj.before.after = self

            obj.before = self

            self.up = obj.up

            self.before = obj.before

        self.after = obj

        return True


class BaseObject(LinkedList2D):
    ...


test = BaseObject()

test2 = BaseObject()

test.insert_under(test2)  # will accept BaseObject

test.get_children()  # hint result is: List[BaseObject]
Related