docs: def kw_fkg_allowing_type_hints(
          namespace: Optional[str],
          fn: Callable,
          to_str: Callable[[Any], str] = repr) -> Callable[[Any], str]:
      """
      As for :func:`fkg_allowing_type_hints`, but allowing keyword arguments.
  
      For ``kwargs`` passed in, we will build a ``dict`` of all argname (key) to
      argvalue (values) pairs, including default args from the argspec, and then
      alphabetize the list before generating the key.
  
      NOTE ALSO that once we have keyword arguments, we should be using
      :func:`repr`, because we need to distinguish
  
      .. code-block:: python
  
          kwargs = {'p': 'another', 'q': 'thing'}
          # ... which compat.string_type will make into
          #         p=another q=thing
          # ... from
          kwargs = {'p': 'another q=thing'}
  
      Also modified to make the cached function unique per INSTANCE for normal
      methods of a class.
      """
  
      namespace = get_namespace(fn, namespace)
  
      sig = inspect.signature(fn)
      parameters = list(sig.parameters.values())  # convert from odict_values
      argnames = [p.name for p in parameters
                  if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD]
      has_self = bool(argnames and argnames[0] in ('self', 'cls'))
  
      if DEBUG_INTERNALS:
          log.debug(
              "At start of kw_fkg_allowing_type_hints: namespace={namespace},"
              "parameters=[{parameters}], argnames={argnames}, "
              "has_self={has_self}, fn={fn}",
              namespace=namespace,
              parameters=", ".join(repr_parameter(p) for p in parameters),
              argnames=repr(argnames),
              has_self=has_self,
              fn=repr(fn),
          )
  
      def generate_key(*args: Any, **kwargs: Any) -> str:
          as_kwargs = {}  # type: Dict[str, Any]
          loose_args = []  # type: List[Any]  # those captured by *args
          # 1. args: get the name as well.
          for idx, arg in enumerate(args):
              if idx >= len(argnames):
                  # positional argument to be scooped up with *args
                  loose_args.append(arg)
              else:
                  # normal plain positional argument
                  if has_self and idx == 0:  # "self" or "cls" initial argument
                      argvalue = hex(id(arg))
                  else:
                      argvalue = arg
                  as_kwargs[argnames[idx]] = argvalue
          # 1b. args with no name
          if loose_args:
              as_kwargs['*args'] = loose_args
              # '*args' is guaranteed not to be a parameter name in its own right
          # 2. kwargs
          as_kwargs.update(kwargs)
          # 3. default values
          for param in parameters:
              if param.default != inspect.Parameter.empty:
                  if param.name not in as_kwargs:
                      as_kwargs[param.name] = param.default
          # 4. sorted by name
          #    ... but also incorporating the name of the argument, because once
          #    we allow the arbitrary **kwargs format, order is no longer
          #    sufficient to discriminate
          #       fn(p="another", q="thing")
          #    from
          #       fn(r="another", s="thing")
          argument_values = ["{k}={v}".format(k=key, v=to_str(as_kwargs[key]))
                             for key in sorted(as_kwargs.keys())]
          key = namespace + '|' + " ".join(argument_values)
          if DEBUG_INTERNALS:
              log.debug("kw_fkg_allowing_type_hints.generate_key() -> {!r}", key)
          return key
  
      return generate_key