Python's Pep 484 Type Annotation For Generator Expression
Solution 1:
All three forms mentioned by you in question are listed as valid alternatives in documentation, Generator expression simply creates a generator that only yields.
Quote 1:
A generator can be annotated by the generic type
Generator[YieldType, SendType, ReturnType]
.
Quote 2:
If your generator will only yield values, set the
SendType
andReturnType
toNone
Quote 3:
Alternatively, annotate your generator as having a return type of either
Iterable[YieldType]
orIterator[YieldType]
:
Solution 2:
Quick note: your function is a "regular function which returns a generator", not a "generator function". To understand the distinction, read this answer.
For your foo
, I suggest using -> Iterator[int]
.
Explanation
It boils down to what kind of interface you want.
First, make yourself familiar with this page in the python documentation where the hierarchy of the most important Python types is defined.
You can see there that these expressions return True
:
import typing as t
issubclass(t.Iterator, t.Iterable)
issubclass(t.Generator, t.Iterator)
You should also notice on the same page that Generator
has methods that Iterator
doesn't have. These methods are send
, throw
and close
(documentation), and they allow you to do more with generators than just simple single passthrough iteration. Check this question for examples of the possibilities with generators: python generator "send" function purpose?
Going back to choosing an interface. If you want others to use the results of your generator function like a generator, i.e.
defgen(limit: int): -> Generator[int, None, None]
for x inrange(limit):
yield x
g = gen(3)
next(g) # => 0
g.send(10) # => 1
Then you should specify -> Generator[int, None, None]
.
But notice that above is nonsense. You in fact can call send
, but it doesn't change the execution because gen
doesn't do anything with sent value (there is nothing like x = yield
).
Knowing that, you can limit the knowledge of people using gen
and define it as -> Iterator[int]
. In this way, you can make a contract with users that "my function returns iterator of integers and you should use it as such". If you later change implementation to, e.g.
defgen(limit: int): -> Iterator[int]
returniter(list(range(limit)))
Those who used a returned object like Generator
(because they peeked implementation) would have their code broken. However, you shouldn't be bothered by that because they used it in a different way to the way specified in your contract. As such, this kind of breakage is not your responsibility.
Put simply, if you end up with Generator[Something, None, None]
(two None
s) then consider Iterable[Something]
or Iterator[Something]
.
The same goes for Iterator
vs Iterable
. If you want your users to be able to use your object only with the iter
function (and thus be used in iteration context e.g. [x for x in g]
), then use Iterable
. If you want them to use both next
and iter
on the object, use Iterator
.
Note
This line of thought applies mostly to the annotated type of returned values. In the case of parameters, you should specify the types according to what interface (read: methods/functions) you want to use on that object inside your function.
Post a Comment for "Python's Pep 484 Type Annotation For Generator Expression"