Python Try Catch Block inside lambda
Yes, it is possible
I put together this little piece of code to demonstrate the possibility to catch exceptions and react to them inside a lambda. It's rather rudimentary and serves more or less as a proof of concept.
Example
>>> print_msg = lambda msg, **print_kwargs: \
... begin(
... print, msg, end='... ', **print_kwargs
... ).\
... rescue(
... (TypeError, AttributeError),
... lambda exc: print(f'just caught "{exc}"! how fun!')
... ).\
... ensure(print, 'ok done.')()
>>> print_msg('check')
check... ok done.
>>> print_msg('check', file=1)
just caught "'int' object has no attribute 'write'"! how fun!
ok done.
>>> print_msg('check', sep=1)
just caught "sep must be None or a string, not int"! how fun!
ok done.
A bit more practical example
modules = filter(None, (
begin(importlib.import_module, modname).rescue(lambda exc: None)()
for modname in module_names
))
Code
from typing import Iterable
class begin:
def __init__(self, fun, *args, **kwargs):
self.fun = fun
self.args = args
self.kwargs = kwargs
self.exception_types_and_handlers = []
self.finalize = None
def rescue(self, exception_types, handler):
if not isinstance(exception_types, Iterable):
exception_types = (exception_types,)
self.exception_types_and_handlers.append((exception_types, handler))
return self
def ensure(self, finalize, *finalize_args, **finalize_kwargs):
if self.finalize is not None:
raise Exception('ensure() called twice')
self.finalize = finalize
self.finalize_args = finalize_args
self.finalize_kwargs = finalize_kwargs
return self
def __call__(self):
try:
return self.fun(*self.args, **self.kwargs)
except BaseException as exc:
handler = self.find_applicable_handler(exc)
if handler is None:
raise
return handler(exc)
finally:
if self.finalize is not None:
self.finalize()
def find_applicable_handler(self, exc):
applicable_handlers = (
handler
for exception_types, handler in self.exception_types_and_handlers
if isinstance(exc, exception_types)
)
return next(applicable_handlers, None)
Nope. A Python lambda can only be a single expression. Use a named function.
It is convenient to write a generic function for converting types:
def tryconvert(value, default, *types):
for t in types:
try:
return t(value)
except (ValueError, TypeError):
continue
return default
Then you can write your lambda:
lambda v: tryconvert(v, 0, int)
You could also write tryconvert()
so it returns a function that takes the value to be converted; then you don't need the lambda:
def tryconvert(default, *types):
def convert(value):
for t in types:
try:
return t(value)
except (ValueError, TypeError):
continue
return default
# set name of conversion function to something more useful
namext = ("_%s_" % default) + "_".join(t.__name__ for t in types)
if hasattr(convert, "__qualname__"): convert.__qualname__ += namext
convert.__name__ += namext
return convert
Now tryconvert(0, int)
returns a function convert_0_int
that takes a value and converts it to an integer, and returns 0
if this can't be done. You can use this function right away (not saving a copy):
mynumber = tryconert(0, int)(value)
Or save it to call it later:
intconvert = tryconvert(0, int)
# later...
mynumber = intconvert(value)
In this specific instance, you can avoid using a try
block like this:
lambda s: int(s) if s.isdigit() else 0
The isdigit()
string method returns true if all the characters of s
are digits. (If you need to accept negative numbers, you will have to do some extra checking.)