Home > Enterprise >  How to annotate results of iteration?
How to annotate results of iteration?

Time:11-27

from __future__ import annotations

from typing import TypeVar, Generic, Type


T = TypeVar('T')


class ListElem(Generic[T]):
    def __init__(self, value: T, nxt: ListElem[T] = None):
        self.value = value
        self.nxt = nxt


class LinkedList(Generic[T]):
    def __init__(self, elem_factory: Type[ListElem], head: ListElem[T] = None):
        self._elem_factory = elem_factory
        self._head = head

    def add(self, value: T):
        elem = self._elem_factory(value, self._head)
        self._head = elem

    def __iter__(self):
        self._next = self._head
        return self

    def __next__(self) -> ListElem[T]:
        if not self._next:
            raise StopIteration

        result = self._next
        self._next = self._next.nxt

        return result


def main():
    lst: LinkedList[int] = LinkedList(ListElem)
    lst.add(5)

    for elem in lst:
        print(elem.value)  # PyCharm see elem like int, not like ListElem


if __name__ == '__main__':
    main()

This annotating __next__ doesnt helps me

How can I annotate that LinkedList return ListElem[T] while iteration, not just T?

If I do it like this

lst: LinkedList[ListElem[int]]

I cannot annotate this int to method

def add(self, value: ?): # T = ListElem[int], but I wanna annotate value just like int
    pass

I dont wanna annotate LinkedList like

lst: LinkedList[ListElem[int], int]

Because int just repeating and annotate same - type of value inside ListElem.value

CodePudding user response:

I would say this is a bug in PyCharm's static type checker.

That elem type should be inferred as ListElem[int] and is correctly inferred as such by mypy for example.

Your code still had a few issues. Mostly type-safety related (since you are already dealing with annotations), but a few other optimizations. One of which incidentally also fixes that PyCharm problem.

The most important one IMO is that you did not use the type parameter of ListElem in the elem_factory annotation. If you do, you'll be able to immediately bind the type argument upon initialization by passing a specified ListElem[int] class. Then you don't need to explicitly annotate lst in your main function. This also happens to satisfy/silence PyCharm.

Here is a version with my proposed changes that passes mypy --strict and should work as intended:

from __future__ import annotations
from typing import Generic, Optional, TypeVar


T = TypeVar("T")


class ListElem(Generic[T]):
    def __init__(self, value: T, nxt: Optional[ListElem[T]] = None) -> None:
        self.value = value
        self.nxt = nxt


class LinkedList(Generic[T]):
    def __init__(self, elem_factory: type[ListElem[T]], head: Optional[ListElem[T]] = None) -> None:
        self._elem_factory = elem_factory
        self._head = head

    def add(self, value: T) -> None:
        self._head = self._elem_factory(value, self._head)

    def __iter__(self) -> LinkedList[T]:
        self._next = self._head
        return self

    def __next__(self) -> ListElem[T]:
        if self._next is None:
            raise StopIteration
        result = self._next
        self._next = self._next.nxt
        return result


def main() -> None:
    lst = LinkedList(ListElem[int])
    lst.add(5)
    for elem in lst:
        print(elem.value)


if __name__ == "__main__":
    main()

However it seems that PyCharm still does not identify elem as the correct type. But at least it is not complaining.

  • Related