docs: def get(self, key, func=None, args=(), kwargs=None, **opts):
          """Manually retrieve a value from the cache, calculating as needed.
  
          Params:
              key -> string to store/retrieve value from.
              func -> callable to generate value if it does not exist, or has
                  expired.
              args -> positional arguments to call the function with.
              kwargs -> keyword arguments to call the function with.
  
          Keyword Params (options):
              These will be combined with region values (as selected by the
              "region" keyword argument, and then selected by "parent" values
              of those regions all the way up the chain to the "default" region).
  
              namespace -> string prefix to apply to the key before get/set.
              lock -> lock constructor. See README.
              expiry -> float unix expiration time.
              max_age -> float number of seconds until the value expires. Only
                  provide expiry OR max_age, not both.
  
          """
          kwargs = kwargs or {}
          key, store = self._expand_opts(key, opts)
  
          # Resolve the etag.
          opts['etag'] = call_or_pass(opts.get('etag') or opts.get('etagger'), args, kwargs)
  
          if not isinstance(key, str):
              raise TypeError('non-string key of type %s' % type(key))
  
          data = store.get(key)
          if data is not None:
              if not self._has_expired(data, opts):
                  return data[VALUE_INDEX]
  
          if func is None:
              return None
  
          # Prioritize passed options over a store's native lock.
          lock_func = opts.get('lock') or getattr(store, 'lock', None)
          lock = lock_func and lock_func(key)
          locked = lock and lock.acquire(opts.get('timeout', DEFAULT_TIMEOUT))
  
          try:
              value = func(*args, **kwargs)
          finally:
              if locked:
                  lock.release()
  
          creation = time()
          expiry = call_or_pass(opts.get('expiry'), args, kwargs)
          max_age = call_or_pass(opts.get('max_age'), args, kwargs)
          if max_age is not None:
              expiry = min(x for x in (expiry, creation + max_age) if x is not None)
  
          # Need to be careful as this is the only place where we do not use the
          # lovely index constants.
          store[key] = (CURRENT_PROTOCOL_VERSION, creation, expiry, opts.get('etag'), value)
  
          return value