Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Yield Statement does not appear to continue where it left off

Tags:

python

yield

I must be overlooking the obvious, but I cannot for the life of me figure out why this yield statement does not continually give me a new datetime value that is 15 minutes later than the previous one. The gettime function is behaving more like a function that "returns" rather than "yields".

import datetime

#function that continually adds 15 minutes to a datetime object
def gettime(caldate):
    while True:
        yield caldate
        caldate += datetime.timedelta(minutes=15)

#initialize a datetime object
nextdate = datetime.datetime(2011, 8, 22, 11,0,0,0)

#call gettime function 25 times.
for i in range(0,25):
    print gettime(nextdate).next()


#output feels like it should be a series of incrementing datetime values 15 minutes apart.
#in actuality, the same result namely:

#2011-08-22 11:00:00

#happens 25 times.
like image 480
Mark Avatar asked Dec 03 '22 02:12

Mark


2 Answers

It's because you're calling the generator each time, starting it anew.

Here is a fixed version:

dates = gettime(nextdate)
for i in range(0, 25):
    print dates.next()   # note that you're not initializing it each time here
                         # just calling next()

That gives me:

2011-08-22 11:00:00
2011-08-22 11:15:00
2011-08-22 11:30:00
2011-08-22 11:45:00
...etc.

An important thing to remember is that a function that yields actually returns a generator, as you can see when we look at my dates object:

>>> dates
<generator object gettime at 0x02A05710>

This is what you can repeatedly call next() on to get the next value. Each time you executed your loop, you were creating a whole new generator and getting the next (in this case, first) value out of it.

like image 126
Daniel DiPaolo Avatar answered Dec 07 '22 22:12

Daniel DiPaolo


Daniel already pointed out that you are creating a fresh generator each time through your loop. It is more usual to loop over a generator, or have another generator consume it, than to explicitly call next() every time.

Here is how you could loop over an islice() of your generator.

from itertools import islice
import datetime

#generator that continually adds 15 minutes to a datetime object
def gettime(caldate):
    while True:
        yield caldate
        caldate += datetime.timedelta(minutes=15)

#initialize a datetime object
nextdate = datetime.datetime(2011, 8, 22, 11,0,0,0)

#call gettime function 25 times.
for the_date in islice(gettime(nextdate),0,25):
    print the_date

You can also simplify this to a generator expression if you wish

from itertools import islice, count
import datetime

#initialize a datetime object
nextdate = datetime.datetime(2011, 8, 22, 11,0,0,0)

#generator expression that continually adds 15 minutes to a datetime object
gettime = (nextdate+datetime.timedelta(minutes=15*i) for i in count())

#call gettime function 25 times.
for the_date in islice(gettime,0,25):
    print the_date
like image 30
John La Rooy Avatar answered Dec 08 '22 00:12

John La Rooy