4

What are the key differences and what and where should each be used?

For example in this example usage of both - and Iterator and Generator seems appropriate to me...but is it True?

Iterator

from typing import Generator, Iterator

def fib(n: int) -> Iterator[int]:
    a :int = 0
    b :int = 1
    while a < n:
        yield a
        a, b = b, a+b

print([x for x in fib(3)])

Generator

from typing import Generator 

def fib(n: int) -> Generator[int, None, None]:
    a :int = 0
    b :int = 1
    while a < n:
        yield a
        a, b = b, a+b

print([x for x in fib(3)])
isherwood
  • 58,414
  • 16
  • 114
  • 157
Oleg Butuzov
  • 4,795
  • 2
  • 24
  • 33
  • Possible duplicate of [Difference between Python's Generators and Iterators](https://stackoverflow.com/questions/2776829/difference-between-pythons-generators-and-iterators) – Chris_Rands Jun 11 '19 at 14:59

1 Answers1

5

Whenever you're not sure what exactly some builtin type is, I recommend checking Typeshed, the repository of type hints for the Python standard library (and some select 3rd party modules). Mypy bakes in a version of typeshed with each release.

For example, here are the definitions of what exactly an Iterator and a Generator are:

@runtime
class Iterator(Iterable[_T_co], Protocol[_T_co]):
    @abstractmethod
    def __next__(self) -> _T_co: ...
    def __iter__(self) -> Iterator[_T_co]: ...

class Generator(Iterator[_T_co], Generic[_T_co, _T_contra, _V_co]):
    @abstractmethod
    def __next__(self) -> _T_co: ...

    @abstractmethod
    def send(self, value: _T_contra) -> _T_co: ...

    @abstractmethod
    def throw(self, typ: Type[BaseException], val: Optional[BaseException] = ...,
              tb: Optional[TracebackType] = ...) -> _T_co: ...

    @abstractmethod
    def close(self) -> None: ...

    @abstractmethod
    def __iter__(self) -> Generator[_T_co, _T_contra, _V_co]: ...

    @property
    def gi_code(self) -> CodeType: ...
    @property
    def gi_frame(self) -> FrameType: ...
    @property
    def gi_running(self) -> bool: ...
    @property
    def gi_yieldfrom(self) -> Optional[Generator]: ...

Notice that:

  1. Iterators only have two methods: __next__ and __iter__ but generators have many more.
  2. Generators are a subtype of Iterators -- every single Generator is also an Iterator, but not vice-versa.

But what does this mean on a high-level?

Well, in short, with iterators, the flow of information is one-way only. When you have an iterator, all you can really do call the __next__ method to get the very next value to be yielded.

In contrast, the flow of information with generators is bidirectional: you can send information back into the generator via the send method.

That's what the other two type parameters are for, actually -- when you do Generator[A, B, C], you're stating that the values you yield are of type A, the values you send into the generator are of type B, and the value that you return from the generator are of type C.

Here's some additional useful reading material:

  1. python generator "send" function purpose?
  2. Difference between Python's Generators and Iterators
  3. Return in generator together with yield in Python 3.3
  4. https://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/

So, when should you use Iterator vs Generator?

Well, in general, you should bias towards using the type that helps the caller understand how you expect the return value to be used.

For example, take your fib example. All you do there is yield values: the flow of information is one-way, and the code is not really set up to accept information from the caller.

So, it would be the most understandable to use Iterator instead of Generator in that case: Iterator best reflects the one-way nature of your fib implementation.

(And if you wrote a generator where the flow of data is meant to be bidirectional, you'd of course need to use Generator instead of Iterator.)

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224