A decorator function to catch exceptions and enter debug mode.

    Args:
        victim (typing.Union(type, function)): either a class or function to
            wrap and debug.
        ignore_exceptions (list): list of classes of exceptions not to catch.
        catch_exception (type): class of exception to catch and debug.
            default is None, meaning catch all exceptions.
        depth (number): how many levels of inner function calls to propagate.

    Returns:
        object. wrapped class or function.

    Note:
        This wrapper avoids recursion by setting a flag to each wrapped item.

def debug(victim=None, ignore_exceptions=(), catch_exception=None, depth=0):
    """A decorator function to catch exceptions and enter debug mode.

    Args:
        victim (typing.Union(type, function)): either a class or function to
            wrap and debug.
        ignore_exceptions (list): list of classes of exceptions not to catch.
        catch_exception (type): class of exception to catch and debug.
            default is None, meaning catch all exceptions.
        depth (number): how many levels of inner function calls to propagate.

    Returns:
        object. wrapped class or function.

    Note:
        This wrapper avoids recursion by setting a flag to each wrapped item.
    """
    if victim is None:
        # Debug is used as a decorator so we need to return wrap function to
        # get the real victim
        def wrapper(real_victim):
            return debug(real_victim, ignore_exceptions,
                         catch_exception, depth)

        return wrapper

    if inspect.isfunction(victim):
        if hasattr(victim, '_ipdebug_wrapped'):
            # Don't wrap the function more than once
            return victim

        _transformer = ErrorsCatchTransformer(
            ignore_exceptions=ignore_exceptions,
            catch_exception=catch_exception,
            depth=depth)

        try:
            # Try to get the source code of the wrapped object.
            sourcelines, start_num = inspect.getsourcelines(victim.__code__)
            indent = re.match(r'\s*', sourcelines[0]).group()
            source = ''.join(l.replace(indent, '', 1) for l in sourcelines)

        except IOError:
            # Worst-case scenario we can only catch errors at a granularity
            # of the whole function
            return victim

        else:
            # If we have access to the source, we can silence errors on a
            # per-expression basis, which is "better"

            old_code_tree = ast.parse(source)
            # Reposition the line numbers to their original value
            ast.increment_lineno(old_code_tree, start_num - 1)
            tree = _transformer.visit(old_code_tree)

            import_debug_cmd = ast.ImportFrom(
                __name__, [ast.alias("start_debugging", None),
                           ast.alias("debug", None)], 0)

            # Add import to the debugger as first command
            tree.body[0].body.insert(0, import_debug_cmd)

            # Add import to the exception classes
            if catch_exception is not None:
                import_exception_cmd = ast.ImportFrom(
                    catch_exception.__module__,
                    [ast.alias(catch_exception.__name__, None)], 0)

                tree.body[0].body.insert(1, import_exception_cmd)

            if ignore_exceptions is not None:
                for exception_class in ignore_exceptions:
                    import_exception_cmd = ast.ImportFrom(
                        exception_class.__module__,
                        [ast.alias(exception_class.__name__, None)], 0)

                    tree.body[0].body.insert(1, import_exception_cmd)

            # Delete the debugger decorator of the function
            del tree.body[0].decorator_list[:]

            # Add pass at the end (to enable debugging the last command)
            pass_cmd = ast.Pass()
            func_body = tree.body[0].body
            pass_cmd.lineno = get_last_lineno(func_body[-1]) + 1
            pass_cmd.col_offset = func_body[-1].col_offset
            func_body.append(pass_cmd)

            # Fix missing line numbers and column offsets before compiling
            for node in ast.walk(tree):
                if not hasattr(node, 'lineno'):
                    node.lineno = 0

            ast.fix_missing_locations(tree)

            # Define the wrapping function object
            function_definition = "def _free_vars_wrapper(): pass"
            wrapping_function = ast.parse(function_definition).body[0]

            # Initialize closure's variables to None
            body_list = [ast.parse("{var} = None".format(var=free_var)).body[0]
                         for free_var in victim.__code__.co_freevars]

            # Add the original function ("victim") to the wrapping function
            body_list.append(tree.body[0])

            wrapping_function.body = body_list

            # Replace original function ("victim") with the wrapping function
            tree.body[0] = wrapping_function

            # Create a new runnable code object to replace the original code
            code = compile(tree, victim.__code__.co_filename, 'exec')
            victim.__code__ = code.co_consts[0].co_consts[1]

            # Set a flag to indicate that the method was wrapped
            victim._ipdebug_wrapped = True

            return victim

    elif inspect.ismethod(victim):
        debug(victim.__func__, ignore_exceptions, catch_exception)
        return victim

    elif isinstance(victim, type):
        # Wrap each method of the class with the debugger
        for name, member in vars(victim).items():
            if isinstance(member, (type, types.FunctionType,
                                   types.LambdaType, types.MethodType)):
                setattr(victim, name,
                        debug(member, ignore_exceptions, catch_exception))

        return victim

    else:
        raise TypeError(
            "Debugger can only wrap functions and classes. "
            "Got object {!r} of type {}".format(victim, type(victim).__name__))