I recently came across the idea of generators in Python, so I made a basic example for myself:
def gen(lim):
print 'This is a generator'
for elem in xrange(lim):
yield elem
yield 'still generator...'
print 'done'
x = gen
print x
x = x(10)
print x
print x.next()
print x.next()
I was wondering if there was any way to iterate through my variable x and have to write out print x.next() 11 times to print everything.
That's the whole point of using a generator in the first place:
for i in x:
print i
This is a generator
0
1
2
3
4
5
6
7
8
9
still generator...
done
Yes. You can actually just iterate through the generator as if it were a list (or other iterable):
x = gen(11)
for i in x:
print i
Calling x.next() is actually not particular to generators — you could do it with a list too if you wanted to. But you don't do it with a list, you use a for loop: same with generators.
You can use for loop to iterate generator.
def gen(lim):
print 'This is a generator'
for elem in xrange(lim):
yield elem
yield 'still generator...'
print 'done'
for x in gen(10):
print x
Related
I've been learning how send method and yield assignment work in generator and met a problem:
def gen():
for i in range(5):
x = yield i
print(x)
s = gen()
print(next(s))
print(s.send(15))
output:
0 #after print(next(s))
15
1
so it prints 15 and 1 after print(s.send(15)). It broke my understanding of yield, because I don't understand why it yields 1 after printing x. I'm wondering if someone knows the answer.
When you call s.send(15) the generator resumes running. The value of yield is the argument to s.send(), so it does x = 15 and the generator prints that. Then the for loop repeats with i = 1, and it does yield i.
The value of s.send() is the next value that's yielded, so print(s.send(15)) prints that 1.
I want to define a function that takes a list as its argument and then returns the elements in order.
For example:
def iterator(lis):
for e in range(len(lis)):
return lis[e]
l=input("Enter list elements:").split()
x=iterator(l)
print(x)
But this just returns the first value of the list as:
Enter list elements:12 23 34
12
How can I print all the values in successive lines?
You can use yield in order to build a generator, here's the official documentation about Generators
What is a generator in Python?
A Python generator is a function which returns a generator iterator
(just an object we can iterate over) by calling yield. yield may be
called with a value, in which case that value is treated as the
"generated" value.
I also want to share an example, be sure to read the comments:
def iterator(lis):
for e in range(len(lis)):
yield lis[e]
l=input("Enter list elements:").split()
# A generator returns an Iterable so you should
# loop to print
for number in iterator(l):
print(number)
# Or use list
result = list(iterator(l))
print(result)
Output
1
2
3
['1', '2', '3']
You probably want yield, as return causes the function call to end immediately. But yield just produces another iterator; you still need to iterate over the result to print them all, rather than simply printing the iterator itself.
def iterator(lis):
for e in range(len(lis)):
yield lis[e]
...
for element in x:
print(element)
Of course, you are pretty much just reimplementing the existing list iterator here; an equivalent definition would be
def iterator(lis):
yield from lis
What you might want instead is to do something like
x = '\n'.join(l)
print(x)
which creates a string by iterating over l and joining the elements using \n. The resulting multiline string can then be printed.
It will print only one element if you do return
def iterator(lis):
for e in range(len(lis)):
return lis[e]
l=input("Enter list elements:").split()
x=iterator(l)
for y in x: print(y)
Use:
def iterator(list):
for e in range(len(list)):
a= list[e]
print(a)
l=a,b,c=input().split()
a=iterator(l)
Use:
[print(i) for i in input("Enter list elements:").split(" ")]
return causes the function to stop after it hits the statement. So your for loop only ever runs once.
You could use yield as mentioned in the other answers, but I really don't think you need a function in this situation. Because the function is just going to return the list that it took as an argument. What you should do is something like this:
i = input("Enter list elements: ").split()
for x in i:
print(x)
It's that simple.
def iterator(lis):
for e in range(len(lis)):
print( lis[e])
l=input("Enter list elements:").split()
iterator(l)
if you want to do some operations for each item in the list, then you should accomodate those within the function.
Use of print instead of return will give you the expected output..try it once
Is it possible to use a generator or iterator in a while loop in Python? For example, something like:
i = iter(range(10))
while next(i):
# your code
The point of this would be to build iteration into the while loop statement, making it similar to a for loop, with the difference being that you can now additional logic into the while statement:
i = iter(range(10))
while next(i) and {some other logic}:
# your code
It then becomes a nice for loop/while loop hybrid.
Does anyone know how to do this?
In Python >= 3.8, you can do the following, using assignment expressions:
i = iter(range(10))
while (x := next(i, None)) is not None and x < 5:
print(x)
In Python < 3.8 you can use itertools.takewhile:
from itertools import takewhile
i = iter(range(10))
for x in takewhile({some logic}, i):
# do stuff
"Some logic" here would be a 1-arg callable receciving whatever next(i) yields:
for x in takewhile(lambda e: 5 > e, i):
print(x)
0
1
2
3
4
There are two problems with while next(i):
Unlike a for loop, the while loop will not catch the StopIteration exception that is raised if there is no next value; you could use next(i, None) to return a "falsey" value in that case, but then the while loop will also stop whenever the iterator returns an actual falsey value
The value returned by next will be consumed and no longer available in the loop's body. (In Python 3.8+, that could be solved with an assignment expression, see other answer.)
Instead, you could use a for loop with itertools.takewhile, testing the current element from the iterable, or just any other condition. This will loop until either the iterable is exhausted, or the condition evaluates to false.
from itertools import takewhile
i = iter(range(10))
r = 0
for x in takewhile(lambda x: r < 10, i):
print("using", x)
r += x
print("result", r)
Output:
using 0
...
using 4
result 10
You just need to arrange for your iterator to return a false-like value when it expires. E.g., if we reverse the range so that it counts down to 0:
>>> i = iter(range(5, -1, -1))
>>> while val := next(i):
... print('doing something here with value', val)
...
This will result in:
doing something here with value 5
doing something here with value 4
doing something here with value 3
doing something here with value 2
doing something here with value 1
a = iter(range(10))
try:
next(a)
while True:
print(next(a))
except StopIteration:
print("Stop iteration")
You can do
a = iter(range(10))
try:
a.next()
while True and {True or False logic}:
print("Bonjour")
a.next()
except StopIteration:
print("a.next() Stop iteration")
Basically, I'm in following situation - I generate a list, e.g.
l = [2*x for x in range(10)]
which I iterate through later on multipletimes, e.g.
for i in l: print i # 0,2,4,6,8,10,12,14,16,18
for i in l: print i # 0,2,4,6,8,10,12,14,16,18
for i in l: print i # 0,2,4,6,8,10,12,14,16,18
The problem is that the list is way too large to fit into memory, hence I use its generator form, i.e.:
l = (2*x for x in range(10))
However, after this construction only first iteration works:
for i in l: print i # 0,2,4,6,8,10,12,14,16,18
for i in l: print i #
for i in l: print i #
Where is the problem? How may I iterate through it multipletimes?
Your generator is exhausted the first time. You should recreate your generator each time to renew it:
l = (2*x for x in range(10))
for i in l: print i
l = (2*x for x in range(10))
for i in l: print i
(Note: you should use xrange in python 2 because range creates a list in memory)
You can create also a shortcut function to help you or even a generator function:
def gen():
for i in range(10):
yield 2 * i
and then:
for i in gen(): print i
for i in gen(): print i
You can also iterate on the generator directly:
for i in (2*x for x in range(10)): print i
for i in (2*x for x in range(10)): print i
...
I have the following code:
import itertools
for c in ((yield from bin(n)[2:]) for n in range(10)):
print(c)
The output is:
0
None
1
None
1
0
None
1
1
None
... etc. Why do the Nones appear? If I instead have:
def hmm():
for n in range(10):
yield from bin(n)[2:]
for c in hmm():
print(c)
Then I get what I would expect:
0
1
1
0
1
1
... etc. Further, is there a way to write it as the generator expression to get the same result as the latter?
yield is an expression, and its value is whatever is sent into the generator with send. If nothing is sent in, the value of yield is None. In your example yield from yields the values from the list, but the value of the yield from expression itself is None, which is yielded at each iteration of the enclosing generator expression (i.e., every value of range(10)).
Your example is equivalent to:
def hmm():
for n in range(10):
yield (yield from bin(n)[2:])
for item in hmm():
print(item)
Note the extra yield.
You will always have this issue if you try to use yield in a generator expression, because the generator expression already yields its target values, so if you add an explicit yield, you are adding an extra expression (the yield expression) whose value will also be output in the generator. In other words, something like (x for x in range(5)) already yields the numbers in range(5); if you do something like ((yield x) for x in range(5)), you're going to get the values of the yield expression in addition to the numbers.
As far as I know, there is no way to get the simple yield from behavior (without extra Nones) using a generator comprehension. For your case, I think you can achieve what you want by using itertools.chain.from_iterable:
for c in itertools.chain.from_iterable(bin(n)[2:] for n in range(10)):
print(c)
(Edit: I realized you can get the yield from behavior in a generator comprehension by using nested for clauses: x for n in range(10) for x in bin(n)[2:]. However, I don't think this is any more readable than using itertools.chain.)