Search code examples
pythonpython-3.xgeneratoryieldcoroutine

Why yield is required for python generator?


After reading answer1 and answer2, purpose of yield still looks unclear.


In this first case, with the below function,

def createGenerator():
   mylist = range(3)
   for i in mylist:
      yield i*i

On invoking createGenerator, below,

myGenerator = createGenerator()

should return object(like (x*x for x in range(3))) of type collections.abc.Generator type, is-a collections.abc.Iterator & collections.abc.Iterable

To iterate over myGenerator object and get first value(0),

next(myGenerator)

would actually make for loop of createGenerator function to internally invoke __iter__(myGenerator) and retrieve collections.abc.Iterator type object( obj(say) ) and then invoke __next__(obj) to get first value(0) followed by the pause of for loop using yield keyword


If this understanding(above) is correct, then,

then, does the below syntax(second case),

def createGenerator():
   return (x*x for x in range(3))
myGen = createGenerator() # returns collections.abc.Generator type object
next(myGen) # next() must internally  invoke __next__(__iter__(myGen)) to provide first value(0) and no need to pause

wouldn't suffice to serve the same purpose(above) and looks more readable? Aren't both syntax memory efficient? If yes, then, when should I use yield keyword? Is there a case, where yield could be a must use?


Solution

  • Try doing this without yield

    def func():
        x = 1
        while 1:
            y = yield x
            x += y
    
    
    f = func()
    f.next()  # Returns 1
    f.send(3)  # returns 4
    f.send(10)  # returns 14
    

    The generator has two important features:

    1. The generator some state (the value of x). Because of this state, this generator could eventually return any number of results without using huge amounts of memory.

    2. Because of the state and the yield, we can provide the generator with information that it uses to compute its next output. That value is assigned to y when we call send.

    I don't think this is possible without yield. That said, I'm pretty sure that anything you can do with a generator function can also be done with a class.

    Here's an example of a class that does exactly the same thing (python 2 syntax):

    class MyGenerator(object):
        def __init__(self):
            self.x = 1
    
        def next(self):
            return self.x
    
        def send(self, y):
            self.x += y
            return self.next()
    

    I didn't implement __iter__ but it's pretty obvious how that should work.