|
31 | 31 |
|
32 | 32 | from .regutil import register_decorator |
33 | 33 | from .lazyutil import passthrough_lazy_args, maybe_force_args, force |
| 34 | +from .arity import arity_includes, UnknownArity |
34 | 35 |
|
35 | 36 | # Only the single-argument form (just f) of the "call" decorator is supported by unpythonic.syntax.util.sort_lambda_decorators. |
36 | 37 | # |
@@ -252,6 +253,75 @@ def raisef(exc, *args, cause=None, **kwargs): |
252 | 253 | else: |
253 | 254 | raise exc |
254 | 255 |
|
| 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 | + |
255 | 325 | def pack(*args): |
256 | 326 | """Multi-argument constructor for tuples. |
257 | 327 |
|
|
0 commit comments