What does "for i in generator():" do? [duplicate] - python

This question already has answers here:
What does the "yield" keyword do in Python?
(51 answers)
Closed 7 years ago.
Can someone explain what each step in this does?
I have never seen "for i in X:" used where X is a generator, and I am failing to understand how the i interacts with the function if it's not being inserted between the ().
def fib():
a, b = 0,1
while True:
yield b
a,b = b, a + b
for i in fib():
print(i)

Any function that contains a yield will return a generator. The for-loop runs that generator to return values one at a time.
When you run:
for i in fib():
print(i)
The actual mechanics of running the generator are:
_iterator = iter(fib())
while True:
try:
i = next(_iterator)
except StopIteration:
break
print(i)
As you can see, the i variable is assigned the result of calling next() on the generator to get the next value.
Hope that makes it clear where the i comes from :-)

for just ranges over the vaue of the expression. If the expression calls a function, then its value is whatever is returned from the function, so the for ranges over the result of that function.
Note that here though fib is not a function, it is a generator. It successively yields the value of each step.

for loop generates disposable variable if you use it like above. For example, a list object is used again and again in a loop but a disposable iterator is deleted automatically after use.
And yield is a term like return which is used in functions. It gives a result and use it again in loop.
Your codes give you the number known as fibonacci.
def fib():
a, b = 0,1 #initially a=0 and b=1
while True: #infinite loop term.
yield b #generate b and use it again.
a,b = b, a + b #a and b are now own their new values.
for i in fib(): #generate i using fib() function. i equals to b also thanks to yield term.
print(i) #i think you known this
if i>100:
break #we have to stop loop because of yield.

To understand this you will have to understand what yield keyword does. Please take a look at this: What yield does?
Now you get an idea that fib() is not a function it is a generator.
So in code:
def fib():
a, b = 0,1
while True:
yield b #from here value of b gets returned to the for statement
a,b = b, a + b
for i in fib():
print(i)
Since While never gets a false value. It keeps running.

Related

Is it possible to call more than one next value of an infinite python generator at once?

Is there any way to get the next n values of a generator without looping or calling next() n times?
The thing that the generator in this case is infinite, and cannot be translated into a list.
Here is the generator function:
def f():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
The following loops both give the desired result, but I would like to know if there is some other method of doing this.
gen = f()
n = 0
while n < 10:
print(next(gen))
n += 1
or..
for n, i in enumerate(f()):
if n < 10:
print(i)
else:
break
There are several ways to do this. One way is to use list comprehension, similar to what you already have above. For instance:
gen = f()
elements = [next(gen) for _ in range(10)]
Another way is to use something like the itertools module, for instance the takeWhile()- or islice()-function.
Also check out How to get the n next values of a generator in a list (python).

Infinite python generator

I have been leaning python and programming for not so long. So you may find my question silly.
I am reviewing generator and try to generate 'yes', 'no' infinitely just to understand the concept.
I have tried this code but having "yes" each time
def yes_or_no():
answer = ["yes","no"]
i=0
while True:
if i >=2:
i=0
yield answer[i]
i+=1
c=next(yes_or_no())
print(c)
print(c)
print(c)
print(c)
yes_no() produces the generator; you want to call next on the same generator each time, rather than printing the same first element over and over.
c = yes_no()
print(next(c))
print(next(c))
# etc.
That said, there's no need for a separate counter; just yield yes, then yield no, then repeat.
def yes_or_no():
while True:
yield "yes"
yield "no"
You need to initialize the generator and then call next on the initialized generator object:
c = yes_or_no()
Now you need to call next on c:
print(next(c))
print(next(c))
In your current code c=next(yes_or_no()):
yes_or_no() will initialize the generator and calling next on it will get the first yes and you're saving that yes as name c
In the next lines, you're just printing same yes referred by c while doing print(c)
While your function does return a generator and it has been stated by others that all you need to do is iterate over it using a loop or calling next in succession. Python provides you a great library called itertools to do exactly this thing; it's called itertools.cycle. This is all the code you need to replicate your functions ability:
def yes_no():
return itertools.cycle(['yes', 'no'])
And just like others have said, a generator can be iterated over using next or a loop.
>>> c = yes_no()
>>> next(c)
'yes'
>>> next(c)
'no'
...

Generator error in python

I'm still new to generators in python. I was trying out one on my own and tried to something really simple:
def fib(a):
... if a==0 or a==1:return 1
... yield fib(a-1)+fib(a-2)
print(list(fib(5))
This code gave me this error:
TypeError: unsupported operand type(s) for +: 'generator' and 'generator'
Can't generators be used in this manner?
Calling a generator function doesn't produce the next value. It produces a generator object, a specialist version of an iterator object. You have, in effect, something that wraps a paused function.
To get another value from that function, you'd have to call next(iterator) on the object or use something like list(iteratort) or for ... in iterator to loop over the object and get the values out. See What does the "yield" keyword do? for more details.
So here, the you'd have to use next(fib(a-1)) + next(fib(a-2)) to get the two recursive values out. That'll also fail, because your termination case (a == 0 or a == 1) uses return (translated into the value of a StopIteration exception) and not yield; you'd have to fix that too.
And this highlights why your recursive function should not be a generator function. Your function doesn't produce a series of values to iterate over. There's just one result mfor a given argument value. You'd be far better off to just use return and not yield.
If you wanted to generate a sequence of fibonacci numbers, the function argument would need to be seen as a limit; "give me the first n fibonacci numbers". The following iterative function does that:
def first_n_fibonacci(n):
a, b = 0, 1
for i in range(0, n):
a, b = b, a + b
yield a
This would give you a list of the first n fibonacci numbers, as a generator:
>>> f = first_n_fibonacci(5)
>>> f
<generator object first_n_fibonacci at 0x10b2c8678>
>>> next(f)
1
>>> next(f)
1
>>> list(f)
[2, 3, 5]
or you could use the argument to produce all fibonacci values up to a limit, or to produce an endless generator of fibonacci numbers. None of those would require recursion, a loop like the above suffices.
Generators are meant to be used for iterations, and yet by adding two of the returning values from fib you are trying to use it as a scalar value, which is confirmed by your terminal condition (where a equals to 0 or 1) also returning a scalar value.
You should simply use return in this case.
def fib(a):
if a==0 or a==1:return 1
return fib(a-1)+fib(a-2)
print(fib(5))
If you do want to use a generator, you need to think about outputting a sequence, and not a single value. Do you want your fib(a) to output the ath fib number or the the 1st, 2nd, 3rd, 4th, 5th ... ath fib number? If the latter, then generators are good for this.
Here is an example generator for the Fibonacci numbers from the 1st to the nth number.
def fib(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b

When does the execution of the code in a python generator stop?

I am trying to understand the behaviour of the yield statement by building a generator which behaves similarly to the 'enumerate' built-in function but I am witnessing inconsistencies depending on how I iterate through it.
def enumerate(sequence, start=0):
n = start
for elem in sequence:
print("Before the 'yield' statement in the generator, n = {}".format(n))
yield n, elem
n += 1
print("After the 'yield' statement in the generator, n = {}".format(n))
My understanding of generators is that the execution of the code will stop once a yield statement has been reached, upon which it returns a value. This matches what I get with the script below.
a = 'foo'
b = enumerate(a)
n1,v1 = next(b)
print('n1 = {}, v1 = {}\n'.format(n1,v1))
n2,v2 = next(b)
print('n2 = {}, v2 = {}'.format(n2,v2))
In this case, the generator seems to stop exactly at the yield statement and resumes in the n+=1 one with the second 'next' statement:
Before the 'yield' statement in the generator, n = 0
n1 = 0, v1 = f
After the 'yield' statement in the generator, n = 1
Before the 'yield' statement in the generator, n = 1
n2 = 1, v2 = o
However, if I use the for loop below, the generator does not seem to stop at the yield statement.
for n,v in enumerate(a[0:1]):
print('n = {}, v = {}'.format(n,v))
This is what I get:
Before the 'yield' statement in the generator, n = 0
n = 0, v = f
After the 'yield' statement in the generator, n = 1
Edit taking comments into account
I realise I'm iterating over just one element, but I was not expecting to see the very last "After the 'yield' statement in the generator" sentence (which appears even if I iterate over ALL the elements.
print('\n\n')
for n,v in enumerate(a):
print('n = {}, v = {}'.format(n,v))
Before the 'yield' statement in the generator, n = 0
n = 0, v = f
After the 'yield' statement in the generator, n = 1
Before the 'yield' statement in the generator, n = 1
n = 1, v = o
After the 'yield' statement in the generator, n = 2
Before the 'yield' statement in the generator, n = 2
n = 2, v = o
After the 'yield' statement in the generator, n = 3
Why does this happen?
The fundamental issue here is that you are confusing the fact that you know when the generator will be exhausted just by looking at it, with the fact that Python can only know by running the code. When Python reaches the yield that you consider to be the last one, it does not actually know that it is the last one. What if your generator looked like this:
def enumeratex(x, start=0):
for elem in x:
yield start, x
start += 1
yield start, None
Here, for reasons no one will ever know, a final None element is returned after the main generator loop. Python would have no way of knowing that the generator is done until you either
Return from the generator.
Raise an error, in which case everything will grind to a halt.
In versions before Python 3.7, generators could raise StopIteration to indicate termination. In fact, a return statement would be equivalent to either raise StopIteration (if returning None) or raise StopIteration(return_value).
So while the exact manner in which you tell Python to end the generator is up to you, you do have to be explicit about it. A yield does not by itself end the generator.
TL;DR
All of the code in a loop in a generator will always run, even after the last value has been yielded because Python can only know it was the last value by actually executing all the code.
the answer lies in understanding what for loop in python does:
It get the iterator (i.e. iter()) of an object and continues until a StopIteration exception is raised.
StopIteration exception is thrown when the code of the generator is done, meaning getting the return statement which exists the function (could be implicit also).
This is why it doesn't stops at yield, it keeps asking for the next yield until the generator is done.

Generators in python not working as expected

I have the following code which is not working for me:
def bitmask_generator(n):
if n > 0:
for x in bitmask_generator(n-1):
yield 1 + (x << 1)
for x in bitmask_generator(n-1):
yield x << 1
...
for x in bitmask_generator(5):
print x
The code is supposed to generate all possible bitmasks with length n. However, it prints nothing. What am I doing wrong here?
Update: adding a print(n) on the first line of bitmask_generator does actually print a bunch of n values.
Your innermost generator will not generate anything; when n > 0 is false, the function just returns. As such, your outer generator functions have nothing to loop over.
Say you called bitmask_generator(1); it would call bitmask_generator(0) twice. Each such generator would produce an empty sequence, so both for loops in the bitmask_generator(1) stack frame have nothing to loop over, so no yield is ever reached.
You need to add a yield statement before the first for statement. It is a requirement of recursive generators.
def exampleGen(item):
yield item
for x in exampleGen(getNextValueFuntion(x)):
yield x

Categories

Resources