MadPhysicist
MadPhysicist

Reputation: 5831

Passing Multiple Values into Generator in Python

I am wondering if it is possible to pass several values into a generator in Python. I found an example where one is passed:

def counter(maximum):
    i = 0
    while i < maximum:
        val = (yield i)
        # If value provided, change counter
        if val is not None:
            i = val
        else:
            i += 1

Executing the above function:

>>> it = counter(10)  
>>> next(it)  
0
>>> next(it)  
1
>>> it.send(8)  
8
>>> next(it)  
9
>>> next(it)  
Traceback (most recent call last):
  File "t.py", line 15, in <module>
    it.next()
StopIteration

The question is two-fold.

The first is how does the counter function know about the variable val? It does not seem to be defined or passed anywhere. Is there a convention that forces us to use this name in case we would like to pass a value to a generator?

The second question is how can we pass multiple values into a generator? Can this be done in a single send() call or are multiple calls required?

Upvotes: 2

Views: 2371

Answers (1)

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

First answer: val is a totally arbitrary name, you can use any valid python identifier here. counter "knows" about it because it explicitely binds it to the value of the (yield i) expression, which will be None if nothing was sent (plain next(it) call) or whatever was passed to it.send().

Second: send() takes a single value, but nothing prevents you from passing a tuple or list (as long as your generator handles it correctly of course).

Edit: answering to your other questions in the comments

Would not the val always be not None except for the last iteration of the loop?

Why ??? the execution of yield <whatever> is triggered either by normal iteration over the generator and by an explicit call to generator.send(something). In the first case, the value of the yield whatever "expression" (well technically yield is a statement but since it can "produce" a value it behaves like an expression here) is None. In the second case it's whatever has been passed to .send().

This has nothing to do with the number of iterations or an inner loop (that may just not exist - you don't have to have an inner loop in a generator).

That is, does the generator "freeze" exactly upon encountering the yield

It suspends its execution on the yield statement, and resume on the next iteration or .send() call:

>>> def gen(x):
...     for i in xrange(x):
...         print "before yield" 
...         wot = (yield x)
...         print "after yield - wot is '%s'" % wot
...         if wot is not None:
...             x = wot
...             print "x is now %s" % x
... 
>>> g = gen(5)
>>> next(g)
before yield
5
>>> next(g)
after yield - wot is 'None'
before yield
5
>>> g.send(10)
after yield - wot is '10'
x is now 10
before yield
10
>>> next(g)
after yield - wot is 'None'
before yield
10

or does it actually go through the conditional, decide upon what the i should be, yield it and then suspend the execution until another next() is invoked?

Answer above - which you could have found out by yourself actually ;)

Upvotes: 2

Related Questions