[Python-Dev] Simple generators, round 2
Neil Schemenauer
nas@arctrix.com
Sat, 17 Mar 2001 18:17:41 -0800
I've got a different implementation. There are no new keywords
and its simpler to wrap a high level interface around the low
interface.
http://arctrix.com/nas/python/generator2.diff
What the patch does:
Split the big for loop and switch statement out of eval_code2
into PyEval_EvalFrame.
Add a new "why" flag for ceval, WHY_SUSPEND. It is similar to
WHY_RETURN except that the frame value stack and the block stack
are not touched. The frame is also marked resumable before
returning (f_stackbottom != NULL).
Add two new methods to frame objects, suspend and resume.
suspend takes one argument which gets attached to the frame
(f_suspendvalue). This tells ceval to suspend as soon as control
gets back to this frame. resume, strangely enough, resumes a
suspended frame. Execution continues at the point it was
suspended. This is done by calling PyEval_EvalFrame on the frame
object.
Make frame_dealloc clean up the stack and decref f_suspendvalue
if it exists.
There are probably still bugs and it slows down ceval too much
but otherwise things are looking good. Here are some examples
(the're a little long and but illustrative). Low level
interface, similar to my last example:
# print 0 to 999
import sys
def g():
for n in range(1000):
f = sys._getframe()
f.suspend((n, f))
return None, None
n, frame = g()
while frame:
print n
n, frame = frame.resume()
Let's build something easier to use:
# Generator.py
import sys
class Generator:
def __init__(self):
self.frame = sys._getframe(1)
self.frame.suspend(self)
def suspend(self, value):
self.frame.suspend(value)
def end(self):
raise IndexError
def __getitem__(self, i):
# fake indices suck, need iterators
return self.frame.resume()
Now let's try Guido's pi example now:
# Prints out the frist 100 digits of pi
from Generator import Generator
def pi():
g = Generator()
k, a, b, a1, b1 = 2L, 4L, 1L, 12L, 4L
while 1:
# Next approximation
p, q, k = k*k, 2L*k+1L, k+1L
a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1
# Print common digits
d, d1 = a/b, a1/b1
while d == d1:
g.suspend(int(d))
a, a1 = 10L*(a%b), 10L*(a1%b1)
d, d1 = a/b, a1/b1
def test():
pi_digits = pi()
for i in range(100):
print pi_digits[i],
if __name__ == "__main__":
test()
Some tree traversals:
from types import TupleType
from Generator import Generator
# (A - B) + C * (E/F)
expr = ("+",
("-", "A", "B"),
("*", "C",
("/", "E", "F")))
def postorder(node):
g = Generator()
if isinstance(node, TupleType):
value, left, right = node
for child in postorder(left):
g.suspend(child)
for child in postorder(right):
g.suspend(child)
g.suspend(value)
else:
g.suspend(node)
g.end()
print "postorder:",
for node in postorder(expr):
print node,
print
This prints:
postorder: A B - C E F / * +
Cheers,
Neil