code: def cached(cache, key=keys.hashkey, lock=None):
      
      def decorator(func):
          if cache is None:
              def wrapper(*args, **kwargs):
                  return func(*args, **kwargs)
          elif lock is None:
              def wrapper(*args, **kwargs):
                  k = key(*args, **kwargs)
                  try:
                      return cache[k]
                  except KeyError:
                      pass  # key not found
                  v = func(*args, **kwargs)
                  try:
                      cache[k] = v
                  except ValueError:
                      pass  # value too large
                  return v
          else:
              def wrapper(*args, **kwargs):
                  k = key(*args, **kwargs)
                  try:
                      with lock:
                          return cache[k]
                  except KeyError:
                      pass  # key not found
                  v = func(*args, **kwargs)
                  try:
                      with lock:
                          cache[k] = v
                  except ValueError:
                      pass  # value too large
                  return v
          return _update_wrapper(wrapper, func)
      return decorator
docs: Decorator to wrap a function with a memoizing callable that saves
      results in a cache.