def recursive_apply(obj, fn, *args, **kwargs):
    """Recursively applies a function to an obj

    Parameters
    ----------
    obj : string, tuple, list, or dict
        Object (leaves must be strings, regardless of type)
    fn : function
        function to be applied to the leaves (strings)

    Returns
    -------
    string, tuple, list, or dict
        Object of the same type as obj, with fn applied to leaves

    """
    if type(obj) in [str, bytes]:
        return fn(obj, *args, **kwargs)#obj.format(**(mapping))
    elif type(obj) == tuple:
        return tuple(recursive_apply(list(obj), fn, *args, **kwargs))
    elif type(obj) == list:
        return [recursive_apply(o, fn, *args, **kwargs) for o in obj]
    elif type(obj) == dict:
        return {k: recursive_apply(v, fn, *args, **kwargs) for k, v in obj.items()}
    else:
        return fn(obj, *args, **kwargs)
        # return obj