import threading
import functools


def _memoize(f):
    """Memoizing decorator for f, which must have exactly 1 hashable
    argument."""
    nothing = object()  # Unique "no value" sentinel object.
    cache = {}
    # Use a reentrant lock so that if f references the resulting wrapper we die
    # with recursion depth exceeded instead of deadlocking.
    lock = threading.RLock()

    @functools.wraps(f)
    def wrapper(arg):
        if cache.get(arg, nothing) is nothing:
            with lock:
                if cache.get(arg, nothing) is nothing:
                    cache[arg] = f(arg)
        return cache[arg]

    return wrapper