import functools


def ignores(exc_type, returns, when=None):
    """Ignores exception thrown by decorated function.

    When the specified exception is raised by the decorated function,
    the value 'returns' is returned instead.

    The exceptions to catch can further be limited by providing a predicate
    which should return 'True' for exceptions that should be ignored.

    Parameters
    ----------
    exc_type : type
        The exception type that should be ignored.
    returns : T
        The value that is returned when an exception is ignored.
    when : callable, optional
        A predicate that can be used to further refine
        the exceptions to be ignored.

    Examples
    --------

    Ignore all `ValueError`s:

    >>> @ignores(ValueError, returns=1)
    ... def foo(e):
    ...     raise e

    >>> foo(ValueError)
    1

    >>> foo(TypeError)
    Traceback (most recent call last):
        ...
    TypeError

    Ignore `ValueError`s with a specific message:

    >>> @ignores(ValueError, returns=1, when=lambda e: str(e) == "Not so bad.")
    ... def bar(e):
    ...     raise e

    >>> bar(ValueError("Bad!"))
    Traceback (most recent call last):
        ...
    ValueError: Bad!

    >>> bar(ValueError("Not so bad."))
    1
    """

    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exc_type as e:
                if when is None or when(e):
                    pass
                else:
                    raise e
                return returns

        return wrapper

    return decorator