Reputation: 511
I'm attempting to add typing to a method that returns a generator. Whenever I run this program with the return type specified, a TypeError is raised.
Adding quotes or removing the typing fixes the error, but this seems like hack. Surely there is a correct way of doing this.
def inbox_files(self) -> "Generator[RecordsFile]":
...
# OR
def inbox_files(self):
...
from typing import Generator, List
from .records_file import RecordsFile
Class Marshaller:
...
def inbox_files(self) -> Generator[RecordsFile]:
return self._search_directory(self._inbox)
def _search_directory(self, directory: str) -> RecordsFile:
for item_name in listdir(directory):
item_path = path.join(item_name, directory)
if path.isdir(item_path):
yield from self._search_directory(item_path)
elif path.isfile(item_path):
yield RecordsFile(item_path)
else:
print(f"[WARN] Unknown item found: {item_path}")
The following stack trace is produced:
Traceback (most recent call last):
File "./bin/data_marshal", line 8, in <module>
from src.app import App
File "./src/app.py", line 9, in <module>
from .marshaller import Marshaller
File "./src/marshaller.py", line 9, in <module>
class Marshaller:
File "./src/marshaller.py", line 29, in Marshaller
def inbox_files(self) -> Generator[RecordsFile]:
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 254, in inner
return func(*args, **kwds)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 630, in __getitem__
_check_generic(self, params)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 208, in _check_generic
raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
TypeError: Too few parameters for typing.Generator; actual 1, expected 3
¯\_(ツ)_/¯
Upvotes: 33
Views: 13471
Reputation: 531993
Update: Python 3.13 now allows for the send and result types to default to None
if not provided.
You have to explicitly specify the send type and the return type, even if both are None
.
def inbox_files(self) -> Generator[RecordsFile,None,None]:
return self._search_directory(self._inbox)
Note that the yield type is what you might think of as the return type. The send type is the type of value you can pass to the generator's send
method. The return type is the type of value that could be embedded in the StopIteration
exception raised by next
after all possible value have been yielded. Consider:
def foo():
yield 3
return "hi"
f = foo()
The first call to next(f)
will return 3; the second will raise StopIteration("hi")
.
)
A generator that you cannot send into or return from is simply an iterable or an iterator (either, apparently can be used).
def inbox_files(self) -> Iterable[RecordsFile]: # Or Iterator[RecordsFile]
return self._search_directory(self._inbox)
_search_directory
itself also returns a generator/iterable, not an instance of RecordsFile
:
def _search_directory(self, directory: str) -> Iterable[RecordsFile]:
Upvotes: 40
Reputation: 397
From Python 3.13 you can write such code:
from collections.abc import Generator
def my_func(arg1) -> Generator[RecordsFile]:
...
which is much simpler.
Upvotes: 1
Reputation: 352
If using Generator[RecordsFile, None, None]
too often is too cumbersome, you can alias a type:
from typing import Generator, TypeVar
T = TypeVar('T')
type Gen[T] = Generator[T, None, None]
# now use `Gen[RecordsFile]` as an alias for `Generator[RecordsFile, None, None]`
Upvotes: 0
Reputation: 11
This answer was useful, but I was confused since I was sure I had used Generator[] with just one parameter in the past and it worked.
I traced it back to using "from __future__ import annotations". Only one parameter seems to be required in that case.
Upvotes: 0