If defaults is a str, updates a dict with None at an optionally dot-notated current:
        'a.b' --> {'a': {'b': None}}
    If defaults is a list, updates a dict with None at each optionally dot-notated current:
        ['a.b', 'a.c'] --> {'a': {'b': None, 'c': None}}
    If defaults is a dict, applies keys as fields and values as defaults:
        {'a.b': 'bbb', 'a.c': 'ccc'} --> {'a': {'b': 'bbb', 'c': 'ccc'}}
        {'a.b.c': 'ccc', 'a.c.d.e': 'eee'} --> {'a': {'b': {'c': 'ccc'}, 'c': {'d': {'e': 'eee'}}}}

def setdefaults(d, defaults):
    """
    If defaults is a str, updates a dict with None at an optionally dot-notated current:
        'a.b' --> {'a': {'b': None}}
    If defaults is a list, updates a dict with None at each optionally dot-notated current:
        ['a.b', 'a.c'] --> {'a': {'b': None, 'c': None}}
    If defaults is a dict, applies keys as fields and values as defaults:
        {'a.b': 'bbb', 'a.c': 'ccc'} --> {'a': {'b': 'bbb', 'c': 'ccc'}}
        {'a.b.c': 'ccc', 'a.c.d.e': 'eee'} --> {'a': {'b': {'c': 'ccc'}, 'c': {'d': {'e': 'eee'}}}}
    """

    if not isinstance(d, dict) or defaults is None:
        return d
    elif isinstance(defaults, _wrap_types):
        return setdefaults(d, [defaults])  # Wrap in list for consistent behavior

    use_none = not any(isinstance(s, dict) for s in defaults)

    # Accumulate (current, remaining) pairs to be applied to d

    accumulated = {}
    for current, remaining in _to_key_val_pairs(defaults):
        accumulated.setdefault(current, [])
        if remaining is not None:
            accumulated[current].append(remaining)

    # Update d with accumulated pairs, handling further nested dot-notated keys

    for current, remaining in iteritems(accumulated):
        if use_none:

            # Apply None value for what remains of the dot notated key
            defaults = setdefaults(d.get(current, {}), remaining) if remaining else None
            if defaults and isinstance(d.get(current), dict):
                d[current].update(defaults)
            else:
                d.setdefault(current, defaults)

        else:
            next_up = remaining[0] if remaining else None

            if '.' in current:
                # Split on the dot and process next segment
                k, s = current.split('.', 1)
                d.setdefault(k, {})
                setdefaults(d[k], [{s: next_up}])
            elif isinstance(next_up, STRING_TYPES):
                # Set a string value directly
                d.setdefault(current, next_up)
            else:
                # Process a dict value or just set to the value in next_up
                d.setdefault(current, setdefaults({}, remaining) or next_up)

    return d