Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper use Generator typing

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

¯\_(ツ)_/¯

like image 201
mick.io Avatar asked Aug 05 '19 17:08

mick.io


People also ask

What is the use of generator in Python?

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.

What is the send type of a generator Python?

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 .

What does a generator return?

Simply speaking, a generator is a function that returns an object (iterator) which we can iterate over (one value at a time).

What is a generator expression Python?

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.


1 Answers

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]:
like image 163
chepner Avatar answered Sep 17 '22 20:09

chepner