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: MEDIUM
Tags: Fundamentals

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


class suppress:

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

	def __enter__(self):
		pass

	def __exit__(self, exctype, excinst, exctb):
		self.excinst = excinst
		return exctype is not None and issubclass(exctype, self._exceptions)

def handle(func, success, failure, *args):
	result = None
	ei = None
	for exc in args:
		with suppress(*args):
			scm = suppress(exc)
			with scm:
				result = func()
				break
			if result is None:
				ei = scm.excinst
				break
		continue
	if result is not None:
		success(func, result)
		return
	if ei:
		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)