summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
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)")