summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorPhillip J. Eby <pje@telecommunity.com>2005-08-02 00:46:46 (GMT)
committerPhillip J. Eby <pje@telecommunity.com>2005-08-02 00:46:46 (GMT)
commit0d6615fd29063bdaccb13e1fbae542fb666d8728 (patch)
tree0f18d41e2cb8831c9d244ab6586f9f8377592c67 /Lib
parentd794666048510deca0d4987a4c74d0fca85be411 (diff)
downloadcpython-0d6615fd29063bdaccb13e1fbae542fb666d8728.zip
cpython-0d6615fd29063bdaccb13e1fbae542fb666d8728.tar.gz
cpython-0d6615fd29063bdaccb13e1fbae542fb666d8728.tar.bz2
PEP 342 implementation. Per Guido's comments, the generator throw()
method still needs to support string exceptions, and allow None for the third argument. Documentation updates are needed, too.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/compiler/transformer.py12
-rwxr-xr-xLib/symbol.py1
-rw-r--r--Lib/test/test_generators.py276
-rw-r--r--Lib/test/test_genexps.py2
-rw-r--r--Lib/test/test_parser.py11
5 files changed, 285 insertions, 17 deletions
diff --git a/Lib/compiler/transformer.py b/Lib/compiler/transformer.py
index 5844bb5..4759280 100644
--- a/Lib/compiler/transformer.py
+++ b/Lib/compiler/transformer.py
@@ -403,7 +403,15 @@ class Transformer:
return Return(self.com_node(nodelist[1]), lineno=nodelist[0][2])
def yield_stmt(self, nodelist):
- return Yield(self.com_node(nodelist[1]), lineno=nodelist[0][2])
+ expr = self.com_node(nodelist[0])
+ return Discard(expr, lineno=expr.lineno)
+
+ def yield_expr(self, nodelist):
+ if len(nodelist)>1:
+ value = nodelist[1]
+ else:
+ value = Const(None)
+ return Yield(self.com_node(value), lineno=nodelist[0][2])
def raise_stmt(self, nodelist):
# raise: [test [',' test [',' test]]]
@@ -1402,6 +1410,8 @@ _legal_node_types = [
if hasattr(symbol, 'yield_stmt'):
_legal_node_types.append(symbol.yield_stmt)
+if hasattr(symbol, 'yield_expr'):
+ _legal_node_types.append(symbol.yield_expr)
_assign_types = [
symbol.test,
diff --git a/Lib/symbol.py b/Lib/symbol.py
index eaf5a25..8219008 100755
--- a/Lib/symbol.py
+++ b/Lib/symbol.py
@@ -88,6 +88,7 @@ gen_for = 330
gen_if = 331
testlist1 = 332
encoding_decl = 333
+yield_expr = 334
#--end constants--
sym_name = {}
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 109af73..97cdd2a 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -382,7 +382,7 @@ From the Iterators list, about the types of these things.
>>> type(i)
<type 'generator'>
>>> [s for s in dir(i) if not s.startswith('_')]
-['gi_frame', 'gi_running', 'next']
+['close', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
>>> print i.next.__doc__
x.next() -> the next value, or raise StopIteration
>>> iter(i) is i
@@ -421,6 +421,7 @@ Subject: Re: PEP 255: Simple Generators
... self.name = name
... self.parent = None
... self.generator = self.generate()
+... self.close = self.generator.close
...
... def generate(self):
... while not self.parent:
@@ -482,6 +483,9 @@ merged F into A
A->A B->G C->A D->G E->G F->A G->G H->G I->A J->G K->A L->A M->G
merged A into G
A->G B->G C->G D->G E->G F->G G->G H->G I->G J->G K->G L->G M->G
+
+>>> for s in sets: s.close() # break cycles
+
"""
# Emacs turd '
@@ -589,6 +593,7 @@ arguments are iterable -- a LazyList is the same as a generator to times().
... def __init__(self, g):
... self.sofar = []
... self.fetch = g.next
+... self.close = g.close
...
... def __getitem__(self, i):
... sofar, fetch = self.sofar, self.fetch
@@ -619,6 +624,7 @@ efficient.
[200, 216, 225, 240, 243, 250, 256, 270, 288, 300, 320, 324, 360, 375, 384]
[400, 405, 432, 450, 480, 486, 500, 512, 540, 576, 600, 625, 640, 648, 675]
+>>> m235.close()
Ye olde Fibonacci generator, LazyList style.
@@ -642,6 +648,7 @@ Ye olde Fibonacci generator, LazyList style.
>>> fib = LazyList(fibgen(1, 2))
>>> firstn(iter(fib), 17)
[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584]
+>>> fib.close()
"""
# syntax_tests mostly provokes SyntaxErrors. Also fiddling with #if 0
@@ -672,7 +679,7 @@ Traceback (most recent call last):
..
SyntaxError: 'return' with argument inside generator (<doctest test.test_generators.__test__.syntax[2]>, line 3)
-This one is fine:
+These are fine:
>>> def f():
... yield 1
@@ -683,9 +690,6 @@ This one is fine:
... yield 1
... finally:
... pass
-Traceback (most recent call last):
- ..
-SyntaxError: 'yield' not allowed in a 'try' block with a 'finally' clause (<doctest test.test_generators.__test__.syntax[4]>, line 3)
>>> def f():
... try:
@@ -697,11 +701,6 @@ SyntaxError: 'yield' not allowed in a 'try' block with a 'finally' clause (<doct
... pass
... finally:
... pass
-Traceback (most recent call last):
- ...
-SyntaxError: 'yield' not allowed in a 'try' block with a 'finally' clause (<doctest test.test_generators.__test__.syntax[5]>, line 6)
-
-But this is fine:
>>> def f():
... try:
@@ -722,14 +721,16 @@ But this is fine:
>>> def f():
... yield
-Traceback (most recent call last):
-SyntaxError: invalid syntax
+>>> type(f())
+<type 'generator'>
+
>>> def f():
... if 0:
... yield
-Traceback (most recent call last):
-SyntaxError: invalid syntax
+>>> type(f())
+<type 'generator'>
+
>>> def f():
... if 0:
@@ -805,7 +806,7 @@ SyntaxError: invalid syntax
... if 0:
... yield 2 # because it's a generator
Traceback (most recent call last):
-SyntaxError: 'return' with argument inside generator (<doctest test.test_generators.__test__.syntax[22]>, line 8)
+SyntaxError: 'return' with argument inside generator (<doctest test.test_generators.__test__.syntax[24]>, line 8)
This one caused a crash (see SF bug 567538):
@@ -1383,6 +1384,250 @@ True
"""
+coroutine_tests = """\
+Sending a value into a started generator:
+
+>>> def f():
+... print (yield 1)
+... yield 2
+>>> g = f()
+>>> g.next()
+1
+>>> g.send(42)
+42
+2
+
+Sending a value into a new generator produces a TypeError:
+
+>>> f().send("foo")
+Traceback (most recent call last):
+...
+TypeError: can't send non-None value to a just-started generator
+
+
+Yield by itself yields None:
+
+>>> def f(): yield
+>>> list(f())
+[None]
+
+
+
+An obscene abuse of a yield expression within a generator expression:
+
+>>> list((yield 21) for i in range(4))
+[21, None, 21, None, 21, None, 21, None]
+
+And a more sane, but still weird usage:
+
+>>> def f(): list(i for i in [(yield 26)])
+>>> type(f())
+<type 'generator'>
+
+
+Check some syntax errors for yield expressions:
+
+>>> f=lambda: (yield 1),(yield 2)
+Traceback (most recent call last):
+ ...
+SyntaxError: 'yield' outside function (<doctest test.test_generators.__test__.coroutine[10]>, line 1)
+
+>>> def f(): return lambda x=(yield): 1
+Traceback (most recent call last):
+ ...
+SyntaxError: 'return' with argument inside generator (<doctest test.test_generators.__test__.coroutine[11]>, line 1)
+
+>>> def f(): x = yield = y
+Traceback (most recent call last):
+ ...
+SyntaxError: assignment to yield expression not possible (<doctest test.test_generators.__test__.coroutine[12]>, line 1)
+
+
+Now check some throw() conditions:
+
+>>> def f():
+... while True:
+... try:
+... print (yield)
+... except ValueError,v:
+... print "caught ValueError (%s)" % (v),
+>>> import sys
+>>> g = f()
+>>> g.next()
+
+>>> g.throw(ValueError) # type only
+caught ValueError ()
+
+>>> g.throw(ValueError("xyz")) # value only
+caught ValueError (xyz)
+
+>>> g.throw(ValueError, ValueError(1)) # value+matching type
+caught ValueError (1)
+
+>>> g.throw(ValueError, TypeError(1)) # mismatched type, rewrapped
+caught ValueError (1)
+
+>>> g.throw(ValueError(1), "foo") # bad args
+Traceback (most recent call last):
+ ...
+TypeError: instance exception may not have a separate value
+
+>>> g.throw(ValueError, "foo", 23) # bad args
+Traceback (most recent call last):
+ ...
+TypeError: throw() third argument must be a traceback object
+
+>>> def throw(g,exc):
+... try:
+... raise exc
+... except:
+... g.throw(*sys.exc_info())
+>>> throw(g,ValueError) # do it with traceback included
+caught ValueError ()
+
+>>> g.send(1)
+1
+
+>>> throw(g,TypeError) # terminate the generator
+Traceback (most recent call last):
+ ...
+TypeError
+
+>>> print g.gi_frame
+None
+
+>>> g.send(2)
+Traceback (most recent call last):
+ ...
+StopIteration
+
+>>> g.throw(ValueError,6) # throw on closed generator
+Traceback (most recent call last):
+ ...
+ValueError: 6
+
+>>> f().throw(ValueError,7) # throw on just-opened generator
+Traceback (most recent call last):
+ ...
+ValueError: 7
+
+
+Now let's try closing a generator:
+
+>>> def f():
+... try: yield
+... except GeneratorExit:
+... print "exiting"
+
+>>> g = f()
+>>> g.next()
+>>> g.close()
+exiting
+>>> g.close() # should be no-op now
+
+>>> f().close() # close on just-opened generator should be fine
+
+>>> def f(): yield # an even simpler generator
+>>> f().close() # close before opening
+>>> g = f()
+>>> g.next()
+>>> g.close() # close normally
+
+And finalization:
+
+>>> def f():
+... try: yield
+... finally:
+... print "exiting"
+
+>>> g = f()
+>>> g.next()
+>>> del g
+exiting
+
+
+Now let's try some ill-behaved generators:
+
+>>> def f():
+... try: yield
+... except GeneratorExit:
+... yield "foo!"
+>>> g = f()
+>>> g.next()
+>>> g.close()
+Traceback (most recent call last):
+ ...
+RuntimeError: generator ignored GeneratorExit
+>>> g.close()
+
+
+Our ill-behaved code should be invoked during GC:
+
+>>> import sys, StringIO
+>>> old, sys.stderr = sys.stderr, StringIO.StringIO()
+>>> g = f()
+>>> g.next()
+>>> del g
+>>> sys.stderr.getvalue().startswith(
+... "Exception exceptions.RuntimeError: 'generator ignored GeneratorExit' in "
+... )
+True
+>>> sys.stderr = old
+
+
+And errors thrown during closing should propagate:
+
+>>> def f():
+... try: yield
+... except GeneratorExit:
+... raise TypeError("fie!")
+>>> g = f()
+>>> g.next()
+>>> g.close()
+Traceback (most recent call last):
+ ...
+TypeError: fie!
+
+
+Ensure that various yield expression constructs make their
+enclosing function a generator:
+
+>>> def f(): x += yield
+>>> type(f())
+<type 'generator'>
+
+>>> def f(): x = yield
+>>> type(f())
+<type 'generator'>
+
+>>> def f(): lambda x=(yield): 1
+>>> type(f())
+<type 'generator'>
+
+>>> def f(): x=(i for i in (yield) if (yield))
+>>> type(f())
+<type 'generator'>
+
+>>> def f(d): d[(yield "a")] = d[(yield "b")] = 27
+>>> data = [1,2]
+>>> g = f(data)
+>>> type(g)
+<type 'generator'>
+>>> g.send(None)
+'a'
+>>> data
+[1, 2]
+>>> g.send(0)
+'b'
+>>> data
+[27, 2]
+>>> try: g.send(1)
+... except StopIteration: pass
+>>> data
+[27, 27]
+
+"""
+
__test__ = {"tut": tutorial_tests,
"pep": pep_tests,
"email": email_tests,
@@ -1390,6 +1635,7 @@ __test__ = {"tut": tutorial_tests,
"syntax": syntax_tests,
"conjoin": conjoin_tests,
"weakref": weakref_tests,
+ "coroutine": coroutine_tests,
}
# Magic test name that regrtest.py invokes *after* importing this module.
diff --git a/Lib/test/test_genexps.py b/Lib/test/test_genexps.py
index 04694f8..7c6fe4a 100644
--- a/Lib/test/test_genexps.py
+++ b/Lib/test/test_genexps.py
@@ -130,7 +130,7 @@ Verify that syntax error's are raised for genexps used as lvalues
>>> (y for y in (1,2)) += 10
Traceback (most recent call last):
...
- SyntaxError: augmented assign to tuple literal or generator expression not possible
+ SyntaxError: augmented assign to tuple literal, yield, or generator expression not possible
diff --git a/Lib/test/test_parser.py b/Lib/test/test_parser.py
index 205c406..d1ace67 100644
--- a/Lib/test/test_parser.py
+++ b/Lib/test/test_parser.py
@@ -29,11 +29,22 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase):
def test_yield_statement(self):
self.check_suite("def f(): yield 1")
+ self.check_suite("def f(): yield")
+ self.check_suite("def f(): x += yield")
+ self.check_suite("def f(): x = yield 1")
+ self.check_suite("def f(): x = y = yield 1")
+ self.check_suite("def f(): x = yield")
+ self.check_suite("def f(): x = y = yield")
+ self.check_suite("def f(): 1 + (yield)*2")
+ self.check_suite("def f(): (yield 1)*2")
self.check_suite("def f(): return; yield 1")
self.check_suite("def f(): yield 1; return")
self.check_suite("def f():\n"
" for x in range(30):\n"
" yield x\n")
+ self.check_suite("def f():\n"
+ " if (yield):\n"
+ " yield x\n")
def test_expressions(self):
self.check_expr("foo(1)")