Memoize aka cache the return output of a function
    given a specific set of arguments

    .. versionedited:: 2016.3.4

    Added **kwargs support.

def memoize(func):
    '''
    Memoize aka cache the return output of a function
    given a specific set of arguments

    .. versionedited:: 2016.3.4

    Added **kwargs support.
    '''
    cache = {}

    @wraps(func)
    def _memoize(*args, **kwargs):
        str_args = []
        for arg in args:
            if not isinstance(arg, six.string_types):
                str_args.append(six.text_type(arg))
            else:
                str_args.append(arg)

        args_ = ','.join(list(str_args) + ['{0}={1}'.format(k, kwargs[k]) for k in sorted(kwargs)])
        if args_ not in cache:
            cache[args_] = func(*args, **kwargs)
        return cache[args_]

    return _memoize