docs: def _get_properties(
      elt, keys=None, local=False, exclude=None, first=False, ctx=None,
      _found_keys=None
  ):
      """Get a dictionary of elt properties.
  
      Such dictionary is filled related to the elt properties, and from all base
          elts properties if not local.
  
      :param elt: properties elt. Not None methods.
      :param ctx: elt ctx from where get properties. Equals elt if None. It
          allows to get function properties related to a class or instance if
          related function is defined in base class.
      :param keys: keys of properties to get from elt. If None, get all
          properties.
      :param bool local: if False, get properties from bases classes and type
          as well.
      :param set exclude: elts from where not get properties.
      :param bool first: get only first property values.
      :param _found_keys: used such as a cache value for result.
  
      :return: dict of properties by name and ...:
  
          - if local or first: value.
          - other cases: [(elt, value)] in order of property definition even if
              multi inheritance do not ensure to respect inheritance order in a
              list.
      :rtype: dict
      """
  
      result = {}
  
      # get the best context
      if ctx is None:
          ctx = find_ctx(elt=elt)
  
      # initialize found keys
      if first and _found_keys is None:
          _found_keys = set()
  
      # get elt properties if exist
      elt_properties = _ctx_elt_properties(elt=elt, ctx=ctx, create=False)
  
      # if elt_properties exists
      if elt_properties is not None:
          # if elt properties is not empty
          # try to add property values in result related to input keys
          properties_to_keep = elt_properties.copy() if keys is None else {}
          # get key values
          if keys is not None:
              for key in keys:
                  if key in elt_properties:
                      if not first or key not in _found_keys:
                          properties_to_keep[key] = elt_properties[key]
                      if first and key not in _found_keys:
                          _found_keys.add(key)
  
          if local:  # in case of local, use properties_to_keep
              result = properties_to_keep
  
          # if not local and not first and properties to keep
          elif not first and properties_to_keep:
              for name in properties_to_keep:
                  property_to_keep = properties_to_keep[name]
                  # add (elt, properties_to_keep) in result
                  if name not in result:
                      result[name] = [(elt, property_to_keep)]
                  else:
                      result[name].append((elt, property_to_keep))
  
          # in case of first, result is properties_to_keep
          elif properties_to_keep:
              result = properties_to_keep
      # check if all keys have been found if first
      keys_left = not first or keys is None or len(keys) != len(_found_keys)
  
      # if not local or keys left
      if not local and keys_left:
          # initialize exclude
          if exclude is None:
              exclude = set()
  
          # add elt in exclude in order to avoid to get elt properties twice
          if isinstance(elt, Hashable):
              exclude.add(elt)
  
          # and search among bases ctx elements
          if ctx is not elt:
              # go to base elts
              try:
                  elt_name = elt.__name__
              except AttributeError:
                  pass
              else:
                  # if elt is a sub attr of ctx
                  if hasattr(ctx, elt_name):
                      if hasattr(ctx, __BASES__):
                          ctx_bases = ctx.__bases__
                      elif hasattr(ctx, __CLASS__):
                          ctx_bases = (ctx.__class__,)
                      else:
                          ctx_bases = ()
                      for base_ctx in ctx_bases:
                          # get base_elt properties
                          base_elt = getattr(base_ctx, elt_name, None)
                          if base_elt is not None:
                              base_properties = _get_properties(
                                  elt=base_elt,
                                  keys=keys,
                                  local=local,
                                  exclude=exclude,
                                  ctx=base_ctx,
                                  _found_keys=_found_keys
                              )
                              # update result with base_properties
                              for name in base_properties:
                                  properties = base_properties[name]
                                  if name not in result:
                                      result[name] = properties
                                  else:
                                      result[name] += properties
  
          else:
              # bases classes
              if hasattr(elt, __BASES__):
                  for base in elt.__bases__:
                      if base not in exclude:
                          base_result = _get_properties(
                              elt=base,
                              keys=keys,
                              local=local,
                              exclude=exclude,
                              ctx=base,
                              _found_keys=_found_keys
                          )
                          # update result with base_result
                          for name in base_result:
                              properties = base_result[name]
                              if name not in result:
                                  result[name] = properties
                              else:
                                  result[name] += properties
  
              # search among type definition
  
              # class
              if hasattr(elt, __CLASS__):
                  elt_class = elt.__class__
                  # get class properties only if elt is not its own class
                  if elt_class is not elt and elt_class not in exclude:
                      elt_class_properties = _get_properties(
                          elt=elt_class,
                          keys=keys,
                          local=local,
                          exclude=exclude,
                          ctx=elt_class,
                          _found_keys=_found_keys
                      )
                      # update result with class result
                      for name in elt_class_properties:
                          properties = elt_class_properties[name]
                          if name not in result:
                              result[name] = properties
                          else:
                              result[name] += properties
  
              # type
              elt_type = type(elt)
              # get elt type properties only if elt is not its type
              if elt_type is not elt and elt_type not in exclude:
                  elt_type_properties = _get_properties(
                      elt=elt_type,
                      keys=keys,
                      local=local,
                      exclude=exclude,
                      ctx=elt_type,
                      _found_keys=_found_keys
                  )
                  # update result with type properties
                  for name in elt_type_properties:
                      properties = elt_type_properties[name]
                      if name not in result:
                          result[name] = properties
                      else:
                          result[name] += properties
  
      return result