def pprint(d, lvlindent=2, initindent=0, delim=':',
           max_width=80, depth=3, no_values=False,
           align_vals=True, print_func=None,
           keycolor=None, compress_lists=None,
           round_floats=None, _dlist=False):
    """ print a nested dict in readable format
        (- denotes an element in a list of dictionaries)

    Parameters
    ----------
    d : object
    lvlindent : int
        additional indentation spaces for each level
    initindent : int
        initial indentation spaces
    delim : str
        delimiter between key and value nodes
    max_width : int
        max character width of each line
    depth : int or None
        maximum levels to display
    no_values : bool
        whether to print values
    align_vals : bool
        whether to align values for each level
    print_func : callable or None
        function to print strings (print if None)
    keycolor : None or str
         if str, color keys by this color,
         allowed: red, green, yellow, blue, magenta, cyan, white
    compress_lists : int
         compress lists/tuples longer than this,
          e.g. [1,1,1,1,1,1] -> [1, 1,..., 1]
    round_floats : int
         significant figures for floats

    Examples
    --------

    >>> d = {'a':{'b':{'c':'Å','de':[4,5,[7,'x'],9]}}}
    >>> pprint(d,depth=None)
    a:
      b:
        c:  Å
        de: [4, 5, [7, x], 9]
    >>> pprint(d,max_width=17,depth=None)
    a:
      b:
        c:  Å
        de: [4, 5,
            [7, x],
            9]
    >>> pprint(d,no_values=True,depth=None)
    a:
      b:
        c:
        de:
    >>> pprint(d,depth=2)
    a:
      b: {...}
    >>> pprint({'a':[1,1,1,1,1,1,1,1]},
    ...        compress_lists=3)
    a: [1, 1, 1, ...(x5)]

    """
    if print_func is None:
        print_func = _default_print_func

    if not is_dict_like(d):
        d = {'': d}
        # print_func('{}'.format(d))
        # return

    extra = lvlindent if _dlist else 0

    def decode_to_str(obj):
        val_string = obj
        if isinstance(obj, list):
            if compress_lists is not None:
                if len(obj) > compress_lists:
                    diff = str(len(obj) - compress_lists)
                    obj = obj[:compress_lists] + ['...(x{})'.format(diff)]
            val_string = '[' + ', '.join([decode_to_str(o) for o in obj]) + ']'
        elif isinstance(obj, tuple):
            if compress_lists is not None:
                if len(obj) > compress_lists:
                    diff = str(len(obj) - compress_lists)
                    obj = list(
                        obj[:compress_lists]) + ['...(x{})'.format(diff)]
            val_string = '(' + ', '.join([decode_to_str(o) for o in obj]) + ')'
        elif isinstance(obj, float) and round_floats is not None:
            round_str = '{0:.' + str(round_floats - 1) + 'E}'
            val_string = str(float(round_str.format(obj)))
        else:
            try:
                val_string = encode(obj, outtype='str')
            except (TypeError, UnicodeError):
                pass
        # convert unicode to str (so no u'' prefix in python 2)
        try:
            return str(val_string)
        except Exception:
            return unicode(val_string)

    if align_vals:
        key_width = 0
        for key, val in d.items():
            if not is_dict_like(val):
                key_str = decode_to_str(key)
                key_width = max(key_width, len(key_str))

    max_depth = depth
    for i, key in enumerate(natural_sort(d.keys())):
        value = d[key]
        if _dlist and i == 0:
            key_str = '- ' + decode_to_str(key)
        elif _dlist:
            key_str = '  ' + decode_to_str(key)
        else:
            key_str = decode_to_str(key)

        if keycolor is not None:
            key_str = colortxt(key_str, keycolor)

        if align_vals:
            key_str = '{0: <{1}} '.format(
                key_str + delim, key_width + len(delim))
        else:
            key_str = '{0}{1} '.format(key_str, delim)

        depth = max_depth if max_depth is not None else 2
        if keycolor is not None:
            key_length = len(_strip_ansi(key_str))
        else:
            key_length = len(key_str)
        key_line = ' ' * initindent + key_str
        new_line = ' ' * initindent + ' ' * key_length

        if depth <= 0:
            continue
        if is_dict_like(value):
            if depth <= 1:
                print_func(' ' * initindent + key_str + '{...}')
            else:
                print_func(' ' * initindent + key_str)
                pprint(value, lvlindent, initindent + lvlindent + extra, delim,
                       max_width,
                       depth=max_depth - 1 if max_depth is not None else None,
                       no_values=no_values, align_vals=align_vals,
                       print_func=print_func, keycolor=keycolor,
                       compress_lists=compress_lists,
                       round_floats=round_floats)
            continue

        if isinstance(value, list):
            if all([is_dict_like(o) for o in value]) and value:
                if depth <= 1:
                    print_func(key_line + '[...]')
                    continue
                print_func(key_line)
                for obj in value:
                    pprint(
                        obj, lvlindent, initindent + lvlindent + extra, delim,
                        max_width,
                        depth=max_depth - 1 if max_depth is not None else None,
                        no_values=no_values, align_vals=align_vals,
                        print_func=print_func, keycolor=keycolor,
                        compress_lists=compress_lists,
                        round_floats=round_floats, _dlist=True)
                continue

        val_string_all = decode_to_str(value) if not no_values else ''
        for i, val_string in enumerate(val_string_all.split('\n')):
            if max_width is not None:
                if len(key_line) + 1 > max_width:
                    raise Exception(
                        'cannot fit keys and data within set max_width')
                # divide into chuncks and join by same indentation
                val_indent = ' ' * (initindent + key_length)
                n = max_width - len(val_indent)
                val_string = val_indent.join(
                    [s + '\n' for s in textwrap.wrap(val_string, n)])[:-1]

            if i == 0:
                print_func(key_line + val_string)
            else:
                print_func(new_line + val_string)