We all know how to handle exceptions in Python. Just use:

    try:
        num = float(input())
    except ValueError:
        print("That's not a number!")
    else:
        print(num)

Code such as this

    def factorial(x, n = 1):
        if x == 0:
          raise ValueError(n)
        factorial(x - 1, n * x)

relies on ridiculous exception misuse, but you can't change it because that would require a complete refactor. Code such as this

    try:
        return int(input("Input a number: ")
    except ValueError:
        return 4 # random number

relies on reasonable exception use - almost all of the Python documentation examples are written in this way.

What if you are using a faulty implementation of Embedded Python that doesn't implement the `try` statement? Where `sys.excepthook` is a hard-coded, unoverrideable value? Where even `__file__` is not defined? How do you use basic functions like `list.index`?

Your task is to write a function that can handle exceptions raised in a program or function _without_ using `try` or `except`. Somehow.

The first argument of your function `handle` will be a `lambda` requiring no parameters. You will call this function and handle any exceptions raised. The second argument will be a callable `success`:

    def success(func, val):
        pass

The third argument will be a callable `failure`:

    def failure(func, exc):
        pass

Subsequent arguments will be exceptions. If instances of these exceptions are raised, you must call the handler and no error message must be printed to `stderr`. If the exception raised is not provided as an argument, it should appear as though the exception was never caught.

Pass the return value of `func` to `success` unless it raises an exception. If it raises an exception that `isinstance` of an exception class passed to `handle`, call `failure` with an instance of the raised exception.

Don't worry about the little things like dealing with the extra arguments to exceptions or maintaining the call stack. Whoever writes code like _that_ deserves the extra work.

## What does "catching an exception" mean?
It means:

  * The exception will not be printed to `stderr`.
  * Code can continue to be executed.
  * The `failure` callable knows what the exception was.

Difficulty: interview

Starter code:

def handle(func, success, failure, *exceptions):
	

Solutions:
def handle(func, success, failure, *exceptions):
    class manager:
        def __enter__(self):
            pass
        def __exit__(self, type, value, traceback):
            if isinstance(value, exceptions):
                failure(func, value)
                return True
            return not value
    with manager():
        success(func, func())


# Steal some contextlib stuff. Teach it to remember the exception instance.
# 3.5 version used, no 3.7+ ABCs.
class suppress:
    """Context manager to suppress specified exceptions
    After the exception is suppressed, execution proceeds with the next
    statement following the with statement.
         with suppress(FileNotFoundError):
             os.remove(somefile)
         # Execution still resumes here if the file was already removed
    """

    def __init__(self, *exceptions):
        self._exceptions = exceptions

    def __enter__(self):
        pass

    def __exit__(self, exctype, excinst, exctb):
        # Unlike isinstance and issubclass, CPython exception handling
        # currently only looks at the concrete type hierarchy (ignoring
        # the instance and subclass checking hooks). While Guido considers
        # that a bug rather than a feature, it's a fairly hard one to fix
        # due to various internal implementation details. suppress provides
        # the simpler issubclass based semantics, rather than trying to
        # exactly reproduce the limitations of the CPython interpreter.
        #
        # See http://bugs.python.org/issue12029 for more details
        self.excinst = excinst
        return exctype is not None and issubclass(exctype, self._exceptions)

def handle(func, success, failure, *args):
    result = None # function result, if any
    ei = None # exception instance, if any

    for exc in args: # let's see if we can get any exception
        with suppress(*args): # muffle everything which we must handle
            scm = suppress(exc) # get an object here for furter extraction of the instance
            with scm:
                # either this fails for currently tested exception, or some other supported one
                result = func()
                break # no need to continue if we're good
            if result is None: # this means we have found our exception
                ei = scm.excinst # grab the saved guts
                break # nothing more to do here
        continue # if we're here, then we have an exception but didn't find which one yet

    if result is not None: # we got something good, call success as needed
        success(func, result)
        return # all done, happy hour time
    
    if ei: # found that bastard instance
        failure(func, ei)

from contextlib import suppress

def handle(func, success, failure, *exceptions):
    class silenced:
        def __enter__(self): return self
        def __exit__(self, type, value, traceback):
            nonlocal ret
            if not type: success(func, ret)
            if isinstance(value, exceptions): failure(func, value)
            
    with suppress(*exceptions), silenced():
        ret = func()

def handle(func, success, failure, *exceptions):
    class CatchError(object):
        __enter__ = lambda self: None
        def __exit__(self, e_type, *_):
            if e_type is not None and isinstance(e_type(), exceptions):
                failure(func, e_type())
                return True
    with CatchError():
        success(func, func())


class HandleException:
    def __init__(self, func, failure, exceptions):
        self.target = func
        self.failure = failure
        self.captures = exceptions

    def __enter__(self):
        pass
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None and issubclass(exc_type, self.captures):
            self.failure(self.target, exc_value)
            return True


def handle(func, success, failure, *exceptions):
    with HandleException(func, failure, exceptions):
        return success(func, func())

class Handler:
    def __init__(self, func, success, failure, *exception_types):
        self.func = func
        self.success = success
        self.failure = failure
        self.exception_types = exception_types
    def __enter__(self):
        pass
    def __exit__(self, exc_type, exc_value, traceback):
        if isinstance(exc_value, self.exception_types):
            self.failure(self.func, exc_value)
            return True

def handle(func, success, failure, *exception_types):
    with Handler(func, success, failure, *exception_types):
        success(func, func())


def handle(func, success, failure, *exceptions):
    try:
        success(func, func())
    except Exception as e:
        for ex in exceptions:
            if isinstance(e, ex):
                failure(func, e)
                return
        raise e

import dis
class Injection():
    def __init__(self, opcode, opname, arg, argval, argrepr, offset, starts_line, is_jump_target):
        self.opname = opname
dis.Instruction = Injection

import contextlib
def handle(func, success, failure, *exceptions):
    with contextlib.suppress(*exceptions):
        return success(func, func())
    return failure(func, ZeroDivisionError())

class ErrorHandler:
    def __init__(self, *exceptions):
        self._exceptions = exceptions
        
    def __enter__(self):
        pass
    
    def __exit__(self, exctype, excinst, exctb):
        self.traceback = exctype
        return exctype and issubclass(exctype, self._exceptions)

def handle(func, success, failure, *exceptions):
    handler = ErrorHandler(*exceptions)
    with handler:
        return success(func, func())
    return failure(func, handler.traceback())


from contextlib import ContextDecorator

class Swallower:
    def __init__(self, exns): self.exns = exns
    def __enter__(self): return self

    def __exit__(self, kls, val, trace):
        if not val:
            self.fail = None
            return True
        elif any(isinstance(val, e) for e in self.exns):
            self.fail = val
            return True
        else:
            return False

def handle(func, success, failure, *exceptions):
    swallower = Swallower(exceptions)
    with swallower:
        val = func()
    
    if swallower.fail:
        failure(func, swallower.fail)
    else:
        success(func, val)