Prints (and/or logs) an exception with relevant info

    Args:
        ex (Exception): exception to print
        msg (str): a message to display to the user
        keys (None): a list of strings denoting variables or expressions of interest
        iswarning (bool): prints as a warning rather than an error if True (defaults to False)
        tb (bool): if True prints the traceback in the error message
        pad_stdout (bool): separate the error message from the rest of stdout with newlines
        prefix (None):
        locals_ (None):
        N (int):
        use_stdout (bool):
        reraise (bool):
        msg_ (None):
        key_list (list): DEPRICATED use keys

    Returns:
        None

def printex(ex, msg='[!?] Caught exception', prefix=None, key_list=[],
            locals_=None, iswarning=False, tb=TB, pad_stdout=True, N=0,
            use_stdout=False, reraise=False, msg_=None, keys=None,
            colored=None):
    """
    Prints (and/or logs) an exception with relevant info

    Args:
        ex (Exception): exception to print
        msg (str): a message to display to the user
        keys (None): a list of strings denoting variables or expressions of interest
        iswarning (bool): prints as a warning rather than an error if True (defaults to False)
        tb (bool): if True prints the traceback in the error message
        pad_stdout (bool): separate the error message from the rest of stdout with newlines
        prefix (None):
        locals_ (None):
        N (int):
        use_stdout (bool):
        reraise (bool):
        msg_ (None):
        key_list (list): DEPRICATED use keys

    Returns:
        None
    """
    import utool as ut
    if isinstance(ex, MemoryError):
        ut.print_resource_usage()
    if keys is not None:
        # shorthand for key_list
        key_list = keys
    # Get error prefix and local info
    if prefix is None:
        prefix = get_caller_prefix(aserror=True, N=N)
    if locals_ is None:
        locals_ = get_parent_frame(N=N).f_locals
    # build exception message
    if msg is True:
        key_list = get_parent_frame().f_locals
        msg = msg_
    exstr = formatex(ex, msg, prefix, key_list, locals_, iswarning, tb=tb, colored=colored)
    # get requested print function
    if use_stdout:
        def print_func(*args):
            msg = ', '.join(list(map(six.text_type, args)))
            sys.stdout.write(msg + '\n')
            sys.stdout.flush()
    else:
        print_func = ut.partial(ut.colorprint, color='yellow' if iswarning else 'red')
        # print_func = print
    if pad_stdout:
        print_func('\n+------\n')
    # print the execption
    print_func(exstr)
    if pad_stdout:
        print_func('\nL______\n')
    # If you dont know where an error is coming from raise-all
    if (reraise and not iswarning) or RAISE_ALL:
        sys.stdout.flush()
        sys.stderr.flush()
        raise ex
    if ut.get_argflag('--exit-on-error'):
        print('WARNING: dont use this flag. Some errors are meant to be caught')
        ut.print_traceback()
        print('REQUESTED EXIT ON ERROR')
        sys.exit(1)