163

According to PEP-484, we should be able to type hinting a generator function as follows:

from typing import Generator

def generate() -> Generator[int, None, None]:
    for i in range(10):
        yield i

for i in generate():
    print(i)

However, the list comprehension gives the following error in PyCharm.

Expected 'collections.Iterable', got 'Generator[int, None, None]' instead less... (⌘F1)

Any idea why PyCharm is considering this as error?


A few clarification after reading some answers. I am using PyCharm Community Edition 2016.3.2 (the latest version) and have imported the typing.Generator (updated in the code). The above code runs just fine, but PyCharm considers this an error:

enter image description here

So, I'm wondering if this is actually an error or an unsupported feature in PyCharm.

neves
  • 33,186
  • 27
  • 159
  • 192
Jinho Choi
  • 1,841
  • 2
  • 10
  • 12
  • What happens when you change your code to this: http://ideone.com/IwHbT0 ? – Ashwini Chaudhary Mar 01 '17 at 12:15
  • 2
    https://youtrack.jetbrains.com/issue/PY-22071 – Ashwini Chaudhary Mar 01 '17 at 12:17
  • 3
    I use Pycharm 2017.1 EAP and it seems to be fixed. – Oleksandr Dashkov Mar 01 '17 at 12:49
  • 1
    The EAP indeed solved this issue; thank you very much. – Jinho Choi Mar 01 '17 at 13:25
  • I am confused... the OP's code works, **and** it is exactly the same code as the #1 answer has, where the author opens with "You need to import the `typing` module", even though the OP *did in fact include the `typing` module*. Was there a massive edit that made this question moot? Not as far as I can tell, though... – Mike Williamson Feb 28 '21 at 21:49
  • 1
    @MikeWilliamson yes, it was an edit: https://stackoverflow.com/revisions/42531143/2. It looks like the real question is PyCharm, not Python. The top answer has been updated to mention that, too. I dunno what should be done with this confusing Q/A per SO standards. – Jacktose Jan 12 '22 at 00:06

3 Answers3

154

You need to import the typing module. As per docs:

The return type of generator functions can be annotated by the generic type Generator[yield_type, send_type, return_type] provided by typing.py module

Try this way instead:

from typing import Generator


def generate() -> Generator[int, None, None]:
    for i in range(10):
        yield i

The above will have the desired result:

l = [i for i in generate()]

Output:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


As pointed out in the comments, you might not use the last version of PyCharm. Try switching to version 2016.3.2 and you might be fine. Unfortunately this is a well-known bug, as per @AshwiniChaudhary comment.

More, the reported issue (for the last version of PyCharm) was submitted on December, last year. They probably fixed it and pushed the modifications into the same version.

  • 1
    Shouldn't that result in `NameError` for `Generator ` for OP? – Ashwini Chaudhary Mar 01 '17 at 12:15
  • @AshwiniChaudhary why would it ? I just tested it and it works exactly as is. –  Mar 01 '17 at 12:16
  • Issue is not with output, issue is with PyCharm complaining about return type. Have you tested it in PyCharm? – Ashwini Chaudhary Mar 01 '17 at 12:17
  • 1
    @AshwiniChaudhary is correct, the OP has already made the import (even though it's not shown in their code snippet) otherwise they'd get a `NameError` – Chris_Rands Mar 01 '17 at 12:19
  • The OP probably needs to update his Pycharm to _2016.3.2_. I've got a warning even with the latest version, but not an error :) –  Mar 01 '17 at 12:21
  • As per the comment here(https://youtrack.jetbrains.com/issue/PY-17890) it is present in 2016.3.2 as well. – Ashwini Chaudhary Mar 01 '17 at 12:23
  • 11
    what's a `send_type`? – theonlygusti Dec 04 '20 at 18:37
  • @theonlygusti it's a type of value used in `generator.send()` function. With Python generators, you can both receive values which are yielded and send values to generators, which will replace the current yield expression. See this SO question: https://stackoverflow.com/questions/19302530/python-generator-send-function-purpose – Michał Góral Jan 21 '22 at 10:27
  • 1
    Is there more modern syntax yet? – theonlygusti Mar 25 '23 at 22:17
108

This isn't a direct answer to the question, but I think it is a better solution.

I'm using the typing specification below, using Iterator[int] instead of Generator. The validation is OK. I think it is a lot clearer. It better describes the code intention and is recommended by Python docs.

from typing import Iterator

def generate() -> Iterator[int]:
    for i in range(10):
        yield i

It would also allow future refactorings if you change your Generator for a list or other iterable.

I'm using Visual Studio Code with PyLance for typing validation. PyCharm mypy should have the same behavior.

If you are using Python 3.10 or superior, change the import command above to:

from collections.abc import Iterator
neves
  • 33,186
  • 27
  • 159
  • 192
  • 26
    I am not sure why this got downvoted. The python doc itself states, that is it possible to use `Iterator[YieldType]` or `Iterable[YieldType]` for generators, which yield only. See https://docs.python.org/3.6/library/typing.html#typing.Generator – defance Oct 04 '21 at 09:43
  • 16
    `from collections.abc import Iterator` is the new preferable way. See https://docs.python.org/3.10/library/typing.html#typing.Iterable – Thiago Lages de Alencar May 07 '22 at 18:37
  • Yeah, much clearer provided you don't use send / return here. – bravmi Sep 15 '22 at 05:34
  • @theonlygusti ok? you tell us: why not? you can do whatever you want. – mad.meesh May 20 '23 at 20:37
2

The Iterator, Generator and Iterable are slightly different in details and carry different type of information, so understanding the difference might help choosing correct one for your code:

  • Iterator is simply an object that implements the iterator protocol.

  • Generator is a more specific as it not only indicates that the function returns an Iterator, but also that it is implemented using a generator (i.e., a function using yield). Generator functions return iterators.

  • Finally Iterable, which is even more general than Iterator as Iterable can be any object that can be iterated over which also includes lists or tuples (and also iterators).

So how to type hint mainly depends on what your code is really doing. I'd use Iterable to indicate that a function returns an object that can be iterated over, without specifying the exact type of iterator. I'd use Iterator for functions that return an iterator, without specifying what type of iterator exactly it is (so it can be a list but can also be Generator). And finally I'd use Generator to explicitly convey that a function returns a generator and possibly specify the types of values that can be sent to or returned from the generator.

Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
  • Thanks Marcin, this really clearly lays out the options. One follow question: you mentioned that "Generator functions return iterators", but also say that "I'd use Generator to explicitly convey that a function returns a generator". Why wouldn't you just use the Iterator signature as you initially said? I suspect that the answer has something to do with bi-directional generators (i.e. yield from) but would love to hear your thoughts. – cozos Apr 24 '23 at 11:14
  • 1
    There are possible use cases where your code should always use generators, for i.e. resource management reasons or so. And the only way to tell the linter so is to use `Generator` hinting, because remaining two, despite being wider in scope, do not provide any information about where they come from. – Marcin Orlowski Apr 24 '23 at 12:56
  • There are a few things incorrect/imprecise about your generator definition: Yes, the `Generator` type is more specific because it is literally a subtype of `Iterator` (which is a subtype of `Iterable`). But `yield` is not exclusive to generators. A function defined with the `yield` statement (called a generator function) _returns_ a generator and due to the subtype relation that returned object is always _also_ an iterator (and an iterable). The main distinction is that `Generator` (unlike its supertype) has two additional type parameters, namely for its **send** and **return** types. – Daniil Fajnberg Apr 25 '23 at 08:40