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
¯\_(ツ)_/¯
Python Generator functions allow you to declare a function that behaves likes an iterator, allowing programmers to make an iterator in a fast, easy, and clean way. An iterator is an object that can be iterated or looped upon. It is used to abstract a container of data to make it behave like an iterable object.
Generators have a powerful tool in the send() method for generator-iterators. This method was defined in PEP 342, and is available since Python version 2.5. The send() method resumes the generator and sends a value that will be used to continue with the next yield .
Simply speaking, a generator is a function that returns an object (iterator) which we can iterate over (one value at a time).
A generator expression is an expression that returns a generator object. Basically, a generator function is a function that contains a yield statement and returns a generator object.
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]:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With