Skip to content

Commit 720c6e8

Browse files
committed
Merge pull request #58 from python-effect/py3-stopiteration-result
Allow `return x` in @Do generators for Python 3 users
2 parents e61b5f3 + 453120e commit 720c6e8

File tree

3 files changed

+35
-3
lines changed

3 files changed

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

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

effect/test_do.py

Lines changed: 11 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,11 @@ 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+
"""The `return x` syntax in Py3 sets the result of the Effect to `x`."""
161+
from effect._test_do_py3 import py3_generator_with_return
162+
eff = py3_generator_with_return()
163+
assert perf(eff) == 2

0 commit comments

Comments
 (0)