lazy evaluation is sometimes too lazy... help please.

K

Ken Pu

Hi, below is the code I thought should create two generates, it[0] =
0,1,2,3,4,5, and it[1] = 0,10,20,30,..., but they turn out to be the
same!!!

from itertools import *
itlist = [0,0]
for i in range(2):
itlist = (x+(i*10) for x in count())

print "what's in the bags:"
print list(islice(itlist[0], 5))
print list(islice(itlist[1], 5))

The output is:
[10, 11, 12, 13, 14]
[10, 11, 12, 13, 14]

I see what Python is doing -- lazy evaluation doesn't evaluate
(x+(i*10) for x in count()) until the end. But is this the right
behaviour? How can I get the output I want:
[0, 1, 2, 3, 4]
[10, 11, 12, 13, 14]

Thanks.

Ken
 
M

Michael Hartl

James said:
Ken said:
Hi, below is the code I thought should create two generates, it[0] =
0,1,2,3,4,5, and it[1] = 0,10,20,30,..., but they turn out to be the
same!!!

from itertools import *
itlist = [0,0]
for i in range(2):
itlist = (x+(i*10) for x in count())

print "what's in the bags:"
print list(islice(itlist[0], 5))
print list(islice(itlist[1], 5))

The output is:
[10, 11, 12, 13, 14]
[10, 11, 12, 13, 14]

I see what Python is doing -- lazy evaluation doesn't evaluate
(x+(i*10) for x in count()) until the end.


It doesn't evaluate it until you ask it to, which is the right
behavior. However, when evaluated, it evaluates "i" also, which is the
last value to which "i" was assigned, namely the integer 1. I'm going
to get flamed pretty hard for this, but it doesn't seem to be the
intuitive behavior to me either. However, in a purely functional
language, you wouldn't be able to construct a list of generators in
this way.

With python, you have to remember to adopt a purely functional design
and then pray for best results. You can store generators in a list,
but they need to be constructed properly. I can't perfectly
transmogrify your code into functional code because I don't think
making the particular anonymous generator you want is possible in
python. However this is how I would make a close approximation:


from itertools import *

def make_gen(i):
for x in count():
yield x + (i * 10)

itlist = [make_gen(i) for i in xrange(2)]

print "what's in the bags:"
print list(islice(itlist[0], 5))
print list(islice(itlist[1], 5))


James

You could just as well use the original expression in make_gen, too:

from itertools import *
def make_gen(i):
return (x + (i*10) for x in count())

itlist = [make_gen(i) for i in xrange(2)]

print "what's in the bags:"
print list(islice(itlist[0], 5))
print list(islice(itlist[1], 5))

what's in the bags:
[0, 1, 2, 3, 4]
[10, 11, 12, 13, 14]
 
A

alex23

James Stroud said:
I'm going to get flamed
pretty hard for this, but it doesn't seem to be the intuitive behavior
to me either.

Given this is the second time this issue has come up today, I'd have
to agree with you.
 
S

Steven D'Aprano

Hi, below is the code I thought should create two generates, it[0] =
0,1,2,3,4,5, and it[1] = 0,10,20,30,..., but they turn out to be the
same!!! [...]
I see what Python is doing -- lazy evaluation doesn't evaluate (x+(i*10)
for x in count()) until the end. But is this the right behaviour? How
can I get the output I want: [0, 1, 2, 3, 4]
[10, 11, 12, 13, 14]

The solution I would use is:

itlist = [0,0]
for i in range(2):
itlist = ( lambda i: (x+(i*10) for x in count()) )(i)


Or pull the lambda out of the loop:


itlist = [0,0]
def gen(i):
return (x+(i*10) for x in count())

for i in range(2):
itlist = gen(i)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top