Skip to content

Commit 14b0de0

Browse files
committed
Add a draft of tryf (handle exceptions in an expression position)
1 parent 34cbab8 commit 14b0de0

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

unpythonic/misc.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from .regutil import register_decorator
3333
from .lazyutil import passthrough_lazy_args, maybe_force_args, force
34+
from .arity import arity_includes, UnknownArity
3435

3536
# Only the single-argument form (just f) of the "call" decorator is supported by unpythonic.syntax.util.sort_lambda_decorators.
3637
#
@@ -252,6 +253,75 @@ def raisef(exc, *args, cause=None, **kwargs):
252253
else:
253254
raise exc
254255

256+
def tryf(body, *handlers, elsef=None, finallyf=None):
257+
"""``try``/``except``/``finally`` as a function.
258+
259+
This allows lambdas to handle exceptions.
260+
261+
``body`` is a thunk (0-argument function) that represents
262+
the body of the ``try`` block.
263+
264+
``handlers`` is ``((excspec, handler), ...)``, where
265+
``excspec`` is either an exception type,
266+
or a tuple of exception types.
267+
``handler`` is a 0-argument or 1-argument
268+
function. If it takes an
269+
argument, it gets the exception
270+
instance.
271+
272+
Handlers are tried in the order specified.
273+
274+
``elsef`` is a thunk that represents the ``else`` block.
275+
276+
``finallyf`` is a thunk that represents the ``finally`` block.
277+
278+
Upon normal completion, the return value of ``tryf`` is
279+
the return value of ``elsef`` if that was specified, otherwise
280+
the return value of ``body``.
281+
282+
If an exception was caught by one of the handlers, the return
283+
value of ``tryf`` is the return value of the exception handler
284+
that ran.
285+
286+
If you need to share variables between ``body`` and ``finallyf``
287+
(which is likely, given what a ``finally`` block is intended
288+
to do), consider wrapping the ``tryf`` in a ``let`` and
289+
storing your variables there.
290+
"""
291+
def takes_arg(f):
292+
try:
293+
if arity_includes(f, 1):
294+
return True
295+
except UnknownArity:
296+
pass
297+
return False
298+
299+
try:
300+
ret = body()
301+
except BaseException as e:
302+
for excspec, handler in handlers:
303+
if isinstance(excspec, tuple): # tuple of exception types
304+
if not all(issubclass(t, BaseException) for t in excspec):
305+
raise ValueError("All elements of a tuple excspec must be exception types")
306+
if any(isinstance(e, t) for t in excspec):
307+
if takes_arg(handler):
308+
return handler(e)
309+
return handler()
310+
elif issubclass(excspec, BaseException): # single exception type
311+
if isinstance(e, excspec):
312+
if takes_arg(handler):
313+
return handler(e)
314+
return handler()
315+
else:
316+
raise ValueError("excspec must be an exception type or tuple of exception types")
317+
else:
318+
if elsef is not None:
319+
return elsef()
320+
return ret
321+
finally:
322+
if finallyf is not None:
323+
finallyf()
324+
255325
def pack(*args):
256326
"""Multi-argument constructor for tuples.
257327

unpythonic/test/test_misc.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from time import sleep
99
import threading
1010

11-
from ..misc import (call, callwith, raisef, pack, namelambda, timer,
11+
from ..misc import (call, callwith, raisef, tryf, pack, namelambda, timer,
1212
getattrrec, setattrrec, Popper, CountingIterator, ulp, slurp,
1313
async_raise)
1414
from ..fun import withself
@@ -121,6 +121,17 @@ def mul3(a, b, c):
121121
else:
122122
assert False
123123

124+
# tryf: handle an exception in an expression position
125+
assert tryf(lambda: "hello") == "hello"
126+
assert tryf(lambda: "hello",
127+
elsef=lambda: "there") == "there"
128+
assert tryf(lambda: myfunc(),
129+
(ValueError, lambda: "got a ValueError")) == "got a ValueError"
130+
assert tryf(lambda: raisef(ValueError("oof")),
131+
(TypeError, lambda: "got a TypeError"),
132+
((TypeError, ValueError), lambda: "got a TypeError or a ValueError"),
133+
(ValueError, lambda: "got a ValueError")) == "got a TypeError or a ValueError"
134+
124135
myzip = lambda lol: map(pack, *lol)
125136
lol = ((1, 2), (3, 4), (5, 6))
126137
assert tuple(myzip(lol)) == ((1, 3, 5), (2, 4, 6))

0 commit comments

Comments
 (0)