def METHOD_NAME(limit=0, thread_local=False):
    assert limit >= 0
    def decorator(func):
        memory = {}
        lock = threading.Lock()
        if limit:
            keys = collections.deque()
            def get(args):
                try:
                    return memory[args]
                except KeyError:
                    with lock:
                        if args not in memory:
                            fargs = args[-1]
                            memory[args] = func(*fargs)
                            keys.append(args)
                            if len(keys) > limit:
                                del memory[keys.popleft()]
                        return memory[args]
        else:
            def get(args):
                if args not in memory:
                    with lock:
                        if args not in memory:
                            fargs = args[-1]
                            memory[args] = func(*fargs)
                return memory[args]
        if thread_local:
            @functools.wraps(func)
            def wrapper(*args):
                th = threading.current_thread()
                return get((th.ident, th.name, args))
        else:
            @functools.wraps(func)
            def wrapper(*args):
                return get(('', '', args))
        return wrapper
    return decorator