Python Try Catch Block inside lambda

Question:

Is it possible to use try catch block inside of a lambda function. I need the lambda function to convert a certain variable into an integer, but not all of the values will be able to be converted into integers.

Asked By: scriptdiddy

||

Answers:

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)
Answered By: kindall

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.)

Answered By: Greg Hewgill

Depending on your need, another approach could be to keep the try:catch outside lambda fn

toint  = lambda x : int(x)
strval = ['3', '']

for s in strval:
    try:
        print 2 + toint(s)
    except ValueError:
        print 2

Output:

5
2
Answered By: user

While there is no general way for handling exceptions in a lambda expression, you can achieve it in a restricted way for at least one kind of exception; throwing a StopIteration from a part of an expression and catching it in another part is achievable; see:

from random import randrange

list((lambda:(yield from (randrange(0,2) or next(iter(())) for _ in (None,))))())

where next(iter(())) raises a StopIteration while yield from will catch it; the expression above randomly returns [] or [1] according to the inner random value (a 0 will raise the exception and a 1 will be normally evaluated).

You can read more about it ta http://baruchel.github.io/python/2018/06/20/python-exceptions-in-lambda/ .

Answered By: Thomas Baruchel

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)
Answered By: kyrill
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.