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:
- Iterators only have two methods:
__next__
and __iter__
but generators have many more.
- 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:
- python generator "send" function purpose?
- Difference between Python's Generators and Iterators
- Return in generator together with yield in Python 3.3
- 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.)