472,358 Members | 1,642 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 472,358 software developers and data experts.

Why don't generators execute until first yield?

Hi!

First a bit of context.

Yesterday I spent a lot of time debugging the following method in a
rather slim database abstraction layer we've developed:

,----
| def selectColumn(self, table, column, where={}, order_by=[], group_by=[]):
| """Performs a SQL select query returning a single column
|
| The column is returned as a list. An exception is thrown if the
| result is not a single column."""
| query = build_select(table, [column], where, order_by, group_by)
| result = DBResult(self.rawQuery(query))
| if result.colcount != 1:
| raise QueryError("Query must return exactly one column", query)
| for row in result.fetchAllRowsAsList():
| yield row[0]
`----

I'd just rewritten the method as a generator rather than returning a
list of results. The following test then failed:

,----
| def testSelectColumnMultipleColumns(self):
| res = self.fdb.selectColumn('db3ut1', ['c1', 'c2'],
| {'c1':(1, 2)}, order_by='c1')
| self.assertRaises(db3.QueryError, self.fdb.selectColumn,
| 'db3ut1', ['c1', 'c2'], {'c1':(1, 2)}, order_by='c1')
`----

I expected this to raise a QueryError due to the result.colcount != 1
constraint being violated (as was the case before), but that isn't the
case. The constraint it not violated until I get the first result from
the generator.

Now to the main point. When a generator function is run, it immediately
returns a generator, and it does not run any code inside the generator.
Not until generator.next() is called is any code inside the generator
executed, giving it traditional lazy evaluation semantics. Why don't
generators follow the usual eager evaluation semantics of Python and
immediately execute up until right before the first yield instead?
Giving generators special case semantics for no good reason is a really
bad idea, so I'm very curious if there is a good reason for it being
this way. With the current semantics it means that errors can pop up at
unexpected times rather than the code failing fast.

Martin
Jun 27 '08 #1
13 1694
On Wed, May 7, 2008 at 2:29 AM, Martin Sand Christensen <ms*@es.aau.dkwrote:
Now to the main point. When a generator function is run, it immediately
returns a generator, and it does not run any code inside the generator.
Not until generator.next() is called is any code inside the generator
executed, giving it traditional lazy evaluation semantics. Why don't
generators follow the usual eager evaluation semantics of Python and
immediately execute up until right before the first yield instead?
Giving generators special case semantics for no good reason is a really
bad idea, so I'm very curious if there is a good reason for it being
this way. With the current semantics it means that errors can pop up at
unexpected times rather than the code failing fast.
Isn't lazy evaluation sort of the whole point of replacing a list with
an iterator? Besides which, running up to the first yield when
instantiated would make the generator's first iteration inconsistent
with the remaining iterations. Consider this somewhat contrived
example:

def printing_iter(stuff):
for item in stuff:
print item
yield item

Clearly, the idea here is to create a generator that wraps another
iterator and prints each item as it yields it. But using your
suggestion, this would instead print the first item at the time the
generator is created, rather than when the first item is actually
iterated over.

If you really want a generator that behaves the way you describe, I
suggest doing something like this:

def myGenerator(args):
immediate_setup_code()

def generator():
for item in actual_generator_loop():
yield item
return generator()
Jun 27 '08 #2
Martin Sand Christensen <ms*@es.aau.dkwrote:
Now to the main point. When a generator function is run, it
immediately
returns a generator, and it does not run any code inside the
generator.
Not until generator.next() is called is any code inside the generator
executed, giving it traditional lazy evaluation semantics. Why don't
generators follow the usual eager evaluation semantics of Python and
immediately execute up until right before the first yield instead?
You mean you expect the semantics of generators to be that when you
create them, or every time you call next() they run until they hit yield
and then (except for the initial run) return the result that was yielded
the time before? It is easy enough to implement that, but could be a bit
confusing for the user.
>>def greedy(fn):
def greedygenerator(*args, **kw):
def delayed():
it = iter(fn(*args, **kw))
try:
res = it.next()
except StopIteration:
yield None
return
yield None
for value in it:
yield res
res = value
yield res
it = delayed()
it.next()
return it
return greedygenerator
>>@greedy
def mygen(n):
for i in range(n):
print i
yield i

>>x = mygen(3)
0
>>list(x)
1
2
[0, 1, 2]
>>x = mygen(0)
list(x)
[]
>>>
Now try:

for command in getCommandsFromUser():
print "the result of that command was", execute(command)

where getCommandsFromUser is a greedy generator that reads from stdin,
and see why generators don't work that way.

Jun 27 '08 #3
>>>>"Ian" == Ian Kelly <ia*********@gmail.comwrites:
IanIsn't lazy evaluation sort of the whole point of replacing a list
Ianwith an iterator? Besides which, running up to the first yield when
Ianinstantiated would make the generator's first iteration
Ianinconsistent with the remaining iterations.

That wasn't my idea, although that may not have come across quite
clearly enough. I wanted the generator to immediately run until right
before the first yield so that the first call to next() would start with
the first yield.

My objection is that generators _by default_ have different semantics
than the rest of the language. Lazy evaluation as a concept is great for
all the benefits it can provide, but, as I've illustrated, strictly lazy
evaluation semantics can be somewhat surprising at times and lead to
problems that are hard to debug if you don't constantly bear the
difference in mind. In this respect, it seems to me that my suggestion
would be an improvement. I'm not any kind of expert on languages,
though, and I may very well be missing a part of the bigger picture that
makes it obvous why things should be as they are.

As for code to slightly change the semantics of generators, that doesn't
really address the issue as I see it: if you're going to apply such code
to your generators, you're probably doing it exactly because you're
aware of the difference in semantics, and you're not going to be
surprised by it. You may still want to change the semantics, but for
reasons that are irrelevant to my point.

Martin
Jun 27 '08 #4
>>>>"Duncan" == Duncan Booth <du**********@invalid.invalidwrites:
[...]
DuncanNow try:
Duncan>
Duncan for command in getCommandsFromUser():
Duncan print "the result of that command was", execute(command)
Duncan>
Duncanwhere getCommandsFromUser is a greedy generator that reads from stdin,
Duncanand see why generators don't work that way.

I don't see a problem unless the generator isn't defined where it's
going to be used. In other similar input bound use cases, such as the
generator iterating over a query result set in my original post, I see
even less of a problem. Maybe I'm simply daft and you need to spell it
out for me. :-)

Martin
Jun 27 '08 #5
Martin Sand Christensen <ms*@es.aau.dkwrote:
>>>>>"Duncan" == Duncan Booth <du**********@invalid.invalidwrites:
[...]
DuncanNow try:
Duncan>
Duncan for command in getCommandsFromUser():
Duncan print "the result of that command was",
execute(command) Duncan>
Duncanwhere getCommandsFromUser is a greedy generator that reads
from stdin, Duncanand see why generators don't work that way.

I don't see a problem unless the generator isn't defined where it's
going to be used. In other similar input bound use cases, such as the
generator iterating over a query result set in my original post, I see
even less of a problem. Maybe I'm simply daft and you need to spell it
out for me. :-)
It does this:
>>@greedy
def getCommandsFromUser():
while True:
yield raw_input('Command?')

>>for cmd in getCommandsFromUser():
print "that was command", cmd
Command?hello
Command?goodbye
that was command hello
Command?wtf
that was command goodbye
Command?

Traceback (most recent call last):
File "<pyshell#56>", line 1, in <module>
for cmd in getCommandsFromUser():
File "<pyshell#42>", line 11, in delayed
for value in it:
File "<pyshell#53>", line 4, in getCommandsFromUser
yield raw_input('Command?')
KeyboardInterrupt
Jun 27 '08 #6
Duncan Booth wrote:
It does this:
>>>@greedy
def getCommandsFromUser():
while True:
yield raw_input('Command?')

>>>for cmd in getCommandsFromUser():
print "that was command", cmd
Command?hello
Command?goodbye
that was command hello
Command?wtf
that was command goodbye
Command?

Not here..
In [7]: def getCommandsFromUser():
while True:
yield raw_input('Command?')
...:
...:

In [10]: for cmd in getCommandsFromUser(): print "that was command", cmd
....:
Command?hi
that was command hi
Command?there
that was command there
Command?wuwuwuw
that was command wuwuwuw
Command?
Jun 27 '08 #7
Marco Mariani wrote:
Not here..
Oh, sorry, I obviously didn't see the @greedy decorator amongst all the
quoting levels.

Anyway, the idea doesn't make much sense to me :)
Jun 27 '08 #8
Marco Mariani <ma***@sferacarta.comwrote:
Duncan Booth wrote:
>It does this:
>>>>@greedy
def getCommandsFromUser():
while True:
yield raw_input('Command?')

>>>>for cmd in getCommandsFromUser():
print "that was command", cmd
Command?hello
Command?goodbye
that was command hello
Command?wtf
that was command goodbye
Command?


Not here..
In [7]: def getCommandsFromUser():
while True:
yield raw_input('Command?')
...:
...:

In [10]: for cmd in getCommandsFromUser(): print "that was command",
cmd
....:
Command?hi
that was command hi
Command?there
that was command there
Command?wuwuwuw
that was command wuwuwuw
Command?
Perhaps if you'd copied all of my code (including the decorator that was
the whole point of it)...

Jun 27 '08 #9
Duncan Booth wrote:
Perhaps if you'd copied all of my code (including the decorator that was
the whole point of it)...
Sure, I missed the point. Python's symbols become quoting levels and
mess up messages.

Anyway, I would loathe to start execution of a generator before starting
to iterate through it. Especially when generators are passed around.
The current behavior makes perfect sense.

Jun 27 '08 #10
On May 7, 7:37*am, Marco Mariani <ma...@sferacarta.comwrote:
Duncan Booth wrote:
Perhaps if you'd copied all of my code (including the decorator that was
the whole point of it)...

Sure, I missed the point. Python's symbols become quoting levels and
mess up messages.

Anyway, I would loathe to start execution of a generator before starting
to iterate through it. Especially when generators are passed around.
The current behavior makes perfect sense.
Question:
>>def f( ):
... print 0
... while 1:
... yield 1
...
>>g= f( )
g.next( )
0
1
>>g.next( )
1
>>g.next( )
1

This might fit the bill:
>>def dropfirst( h ):
... h.next( )
... return h
...
>>g= dropfirst( f( ) )
0
>>g.next( )
1
>>g.next( )
1
>>g.next( )
1

However as dropfirst is dropping a value, both caller -and- cally have
to designate a/the exception. Hold generators are better "first-
dropped", and you hold 'next' inherently causes side effects. @greedy
(from earlier) frees the caller of a responsibility/obligation.

What can follow without a lead?

The definitions may lean harder on the 'generation' as prior to the
'next': generators inherently don't cause side effects.

Or hold, first-dropped is no exception:
>>special= object( )
def f( ):
... print 0
... yield special
... while 1:
... yield 1
...
>>g= f( )
g.next( )
0
<object object at 0x00980470>
>>g.next( )
1
>>g.next( )
1
>>g.next( )
1
Jun 27 '08 #11
Now to the main point. When a generator function is run, it immediately
returns a generator, and it does not run any code inside the generator.
Not until generator.next() is called is any code inside the generator
executed, giving it traditional lazy evaluation semantics. Why don't
generators follow the usual eager evaluation semantics of Python and
immediately execute up until right before the first yield instead?
Giving generators special case semantics for no good reason is a really
bad idea, so I'm very curious if there is a good reason for it being
this way. With the current semantics it means that errors can pop up at
unexpected times rather than the code failing fast.
The semantics of a generator are very clear: on .next(), run until the next
yield is reached and then return the yielded value. Plus of course the
dealing with StopIteration-stuff.

Your scenario would introduce a special-case for the first run, making it
necessary to keep additional state around (possibly introducing GC-issues
on the way), just for the sake of it. And violate the lazyness a generator
is all about. Think of a situation like this:

def g():
while True:
yield time.time()

Obviously you want to yield the time at the moment of .next() being called.
Not something stored from ages ago. If anything that setups the generator
shall be done immediatly, it's easy enough:

def g():
first_result = time.time()
def _g():
yield first_result
while True:
yield time.time()
return _()

Diez
Jun 27 '08 #12
Martin Sand Christensen wrote:
Why don't
generators follow the usual eager evaluation semantics of Python and
immediately execute up until right before the first yield instead?
A great example of why this behavior would defeat some of the purpose of
generators can be found in this amazing PDF presentation:

http://www.dabeaz.com/generators/Generators.pdf
Giving generators special case semantics for no good reason is a really
bad idea, so I'm very curious if there is a good reason for it being
this way. With the current semantics it means that errors can pop up at
unexpected times rather than the code failing fast.
Most assuredly they do have good reason. Consider the cases in the PDF
I just mentioned. Building generators that work on the output of other
generators allows assembling entire pipelines of behavior. A very
powerful feature that would be impossible if the generators had the
semantics you describe.

If you want generators to behave as you suggest they should, then a
conventional for x in blah approach is likely the better way to go.

I use a generator anytime I want to be able to iterate across something
that has a potentially expensive cost, in terms of memory or cpu, to do
all at once.
Jun 27 '08 #13
On May 7, 4:51*pm, Michael Torrie <torr...@gmail.comwrote:
Martin Sand Christensen wrote:
Why don't
generators follow the usual eager evaluation semantics of Python and
immediately execute up until right before the first yield instead?

A great example of why this behavior would defeat some of the purpose of
generators can be found in this amazing PDF presentation:

http://www.dabeaz.com/generators/Generators.pdf
Giving generators special case semantics for no good reason is a really
bad idea, so I'm very curious if there is a good reason for it being
this way. With the current semantics it means that errors can pop up at
unexpected times rather than the code failing fast.

Most assuredly they do have good reason. *Consider the cases in the PDF
I just mentioned. *Building generators that work on the output of other
generators allows assembling entire pipelines of behavior. *A very
powerful feature that would be impossible if the generators had the
semantics you describe.

If you want generators to behave as you suggest they should, then a
conventional for x in blah approach is likely the better way to go.

I use a generator anytime I want to be able to iterate across something
that has a potentially expensive cost, in terms of memory or cpu, to do
all at once.
The amount of concentration you can write in a program in a sitting
(fixed amount of time) is kind of limited. Sounds like @greedy was
the way to go. The recall implementation may have a short in the
future, but isn't functools kind of full? Has wraptools been
written? Is it any different?

Naming for @greedy also comes to question. My humble opinion muscles
glom on to @early vs. @late; @yieldprior; @dropfirst; @cooperative.
Thesaurus.com adds @ahead vs. @behind.

Jun 27 '08 #14

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

23
by: Francis Avila | last post by:
Below is an implementation a 'flattening' recursive generator (take a nested iterator and remove all its nesting). Is this possibly general and useful enough to be included in itertools? (I know...
9
by: Francis Avila | last post by:
A little annoyed one day that I couldn't use the statefulness of generators as "resumable functions", I came across Hettinger's PEP 288 (http://www.python.org/peps/pep-0288.html, still listed as...
3
by: David Stockwell | last post by:
Hi, Section 9.10 of the tutorial discusses the yield keyword. When I tried using it I get the following SyntaxError. What does this error mean? Does it mean we can't use yield in our code? ...
1
by: fishboy | last post by:
Howdy, I'm in middle of a personal project. Eventually it will download multipart binary attachments and look for missing parts on other servers. And so far I've got it to walk a newsgroup and...
0
by: fishboy | last post by:
Howdy, Sorry if this is a double post. First try seemed to go into hyperspace. I'm working on a personal project. It's going to be a multipart binary attachment downloader that will search...
8
by: Timothy Fitz | last post by:
It seems to me that in python, generators are not truly coroutines. I do not understand why. What I see is that generators are used almost exclusively for generation of lists just-in-time. Side...
24
by: Lasse Vågsæther Karlsen | last post by:
I need to merge several sources of values into one stream of values. All of the sources are sorted already and I need to retrieve the values from them all in sorted order. In other words: s1 = ...
6
by: Talin | last post by:
I've been using generators to implement backtracking search for a while now. Unfortunately, my code is large and complex enough (doing unification on math expressions) that its hard to post a...
18
by: jess.austin | last post by:
hi, This seems like a difficult question to answer through testing, so I'm hoping that someone will just know... Suppose I have the following generator, g: def f() i = 0 while True: yield...
0
by: Naresh1 | last post by:
What is WebLogic Admin Training? WebLogic Admin Training is a specialized program designed to equip individuals with the skills and knowledge required to effectively administer and manage Oracle...
0
by: antdb | last post by:
Ⅰ. Advantage of AntDB: hyper-convergence + streaming processing engine In the overall architecture, a new "hyper-convergence" concept was proposed, which integrated multiple engines and...
1
by: Matthew3360 | last post by:
Hi there. I have been struggling to find out how to use a variable as my location in my header redirect function. Here is my code. header("Location:".$urlback); Is this the right layout the...
2
by: Matthew3360 | last post by:
Hi, I have a python app that i want to be able to get variables from a php page on my webserver. My python app is on my computer. How would I make it so the python app could use a http request to get...
0
by: AndyPSV | last post by:
HOW CAN I CREATE AN AI with an .executable file that would suck all files in the folder and on my computerHOW CAN I CREATE AN AI with an .executable file that would suck all files in the folder and...
1
by: Matthew3360 | last post by:
Hi, I have been trying to connect to a local host using php curl. But I am finding it hard to do this. I am doing the curl get request from my web server and have made sure to enable curl. I get a...
0
Oralloy
by: Oralloy | last post by:
Hello Folks, I am trying to hook up a CPU which I designed using SystemC to I/O pins on an FPGA. My problem (spelled failure) is with the synthesis of my design into a bitstream, not the C++...
0
by: Carina712 | last post by:
Setting background colors for Excel documents can help to improve the visual appeal of the document and make it easier to read and understand. Background colors can be used to highlight important...
0
by: Rahul1995seven | last post by:
Introduction: In the realm of programming languages, Python has emerged as a powerhouse. With its simplicity, versatility, and robustness, Python has gained popularity among beginners and experts...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.