def check_type(obj,
               base,
               *,
               name=None,
               func=None,
               allow_none=False,
               default=None,
               error_message=None):
    """Check whether an object is an instance of a base type.

    Parameters
    ----------
    obj : object
        The object to be validated.

    name : str
        The name of `obj` in the calling function.

    base : type or tuple of type
        The base type that `obj` should be an instance of.

    func: callable, optional
        A function to be applied to `obj` if it is of type `base`. If None, no
        function will be applied and `obj` will be returned as-is.

    allow_none : bool, optional
        Indicates whether the value None should be allowed to pass through.

    default : object, optional
        The default value to return if `obj` is None and `allow_none` is True.
        If `default` is not None, it must be of type `base`, and it will have
        `func` applied to it if `func` is not None.

    error_message : str or None, optional
        Custom error message to display if the type is incorrect.

    Returns
    -------
    base type or None
        The validated object.

    Raises
    ------
    TypeError
        If `obj` is not an instance of `base`.

    Examples
    --------
    >>> check_type(1, int)
    1
    >>> check_type(1, (int, str))
    1
    >>> check_type(1, str)
    Traceback (most recent call last):
    ...
    TypeError: Invalid type. Expected: str. Actual: int.
    >>> check_type(1, (str, bool))
    Traceback (most recent call last):
    ...
    TypeError: Invalid type. Expected: (str, bool). Actual: int.
    >>> print(check_type(None, str, allow_none=True))
    None
    >>> check_type(1, str, name='num')
    Traceback (most recent call last):
    ...
    TypeError: Invalid type for parameter 'num'. Expected: str. Actual: int.
    >>> check_type(1, int, func=str)
    '1'
    >>> check_type(1, int, func='not callable')
    Traceback (most recent call last):
    ...
    ValueError: Parameter 'func' must be callable or None.
    >>> check_type(2.0, str, error_message='Not a string!')
    Traceback (most recent call last):
    ...
    TypeError: Not a string!
    >>> check_type(None, int, allow_none=True, default=0)
    0

    """
    if allow_none and obj is None:
        if default is not None:
            return check_type(default,
                              base=base,
                              name=name,
                              func=func,
                              allow_none=False)
        return None

    if isinstance(obj, base):
        if func is None:
            return obj
        elif callable(func):
            return func(obj)
        else:
            raise ValueError('Parameter \'func\' must be callable or None.')

    # Handle wrong type
    if isinstance(base, tuple):
        expect = '(' + ', '.join(cls.__name__ for cls in base) + ')'
    else:
        expect = base.__name__
    actual = type(obj).__name__
    if error_message is None:
        error_message = 'Invalid type'
        if name is not None:
            error_message += f' for parameter \'{name}\''
        error_message += f'. Expected: {expect}. Actual: {actual}.'
    raise TypeError(error_message)