Skip to content

Commit 7c934c1

Browse files
committed
support return in generators for Py3-only users.
1 parent 905020c commit 7c934c1

File tree

3 files changed

+33
-2
lines changed

3 files changed

+33
-2
lines changed

effect/_test_do_py3.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This code only works in Python 3, so it's left out of test_do.py, to be
2+
# optionally imported.
3+
4+
from effect import Constant, Effect
5+
from effect.do import do
6+
7+
8+
@do
9+
def py3_generator_with_return():
10+
yield Effect(Constant(1))
11+
return 2 # noqa

effect/do.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def foo():
3232
yielded Effect will be passed back into the generator as the result of the
3333
``yield`` expression. Yielded :func:`do_return` values will provide the
3434
ultimate result of the Effect that is returned by the decorated function.
35+
Note that :func:`do_return` is only necessary for Python 2 compatibility;
36+
return statements can be used directly in Python 3-only code.
3537
3638
It's important to note that any generator function decorated by ``@do``
3739
will no longer return a generator, but instead it will return an Effect,
@@ -78,6 +80,9 @@ def do_return(val):
7880
@do
7981
def foo():
8082
yield do_return('hello')
83+
84+
If you're writing Python 3-only code, you don't need to use this function,
85+
and can just use the `return` statement as normal.
8186
"""
8287
return _ReturnSentinel(val)
8388

@@ -88,7 +93,7 @@ def _do(result, generator, is_error):
8893
val = generator.throw(*result)
8994
else:
9095
val = generator.send(result)
91-
except StopIteration:
96+
except StopIteration as stop:
9297
# If the generator we're spinning directly raises StopIteration, we'll
9398
# treat it like returning None from the function. But there may be a
9499
# case where some other code is raising StopIteration up through this
@@ -98,7 +103,12 @@ def _do(result, generator, is_error):
98103
if tb.tb_next:
99104
raise
100105
else:
101-
return None
106+
# Python 3 allows you to use `return val` in a generator, which
107+
# will be translated to a `StopIteration` with a `value` attribute
108+
# set to the return value. So we'll return that value as the
109+
# ultimate result of the effect. Python 2 doesn't have the 'value'
110+
# attribute of StopIteration, so we'll fall back to None.
111+
return getattr(stop, 'value', None)
102112
if type(val) is _ReturnSentinel:
103113
return val.result
104114
elif type(val) is Effect:

effect/test_do.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from functools import partial
33

44
from py.test import raises as raises
5+
from py.test import mark
6+
7+
import six
58

69
from testtools import TestCase
710
from testtools.matchers import raises as match_raises, MatchesException
@@ -150,3 +153,10 @@ def f():
150153
eff = f()
151154
with raises(StopIteration):
152155
perf(eff)
156+
157+
158+
@mark.skipif(not six.PY3, reason="Testing a Py3-specific feature")
159+
def test_py3_return():
160+
from effect._test_do_py3 import py3_generator_with_return
161+
eff = py3_generator_with_return()
162+
assert perf(eff) == 2

0 commit comments

Comments
 (0)