docstr: Build a dict of critical :class:`EngineerStep` attributes
  
  Parameters
  ----------
  step_obj: EngineerStep, dict
      Object for which critical :class:`EngineerStep` attributes should be collected
  
  Returns
  -------
  attr_vals: Dict
      Critical :class:`EngineerStep` attributes. If `step_obj` does not have a necessary
      attribute (for `EngineerStep`) or a necessary key (for dict), its value in `attr_vals`
      will be a placeholder object. This is to facilitate comparison, while also ensuring
      missing values will always be considered unequal to other values
  
  Examples
  --------
  >>> def dummy_f(train_inputs, non_train_inputs):
  ...     return train_inputs, non_train_inputs
  >>> es_0 = EngineerStep(dummy_f)
  >>> EngineerStep.get_comparison_attrs(es_0)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  {'name': 'dummy_f',
   'f': <function dummy_f at ...>,
   'params': ('train_inputs', 'non_train_inputs'),
   'stage': 'intra_cv',
   'do_validate': False}
  >>> EngineerStep.get_comparison_attrs(
  ...     dict(foo="hello", f=dummy_f, params=["all_inputs", "all_targets"], stage="pre_cv")
  ... )  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  {'name': <object object at ...>,
   'f': <function dummy_f at ...>,
   'params': ('all_inputs', 'all_targets'),
   'stage': 'pre_cv',
   'do_validate': <object object at ...>}
code: def get_comparison_attrs(
      step_obj: Union["EngineerStep", dict]
  ) -> dict:
      """Build a dict of critical :class:`EngineerStep` attributes
  
      Parameters
      ----------
      step_obj: EngineerStep, dict
          Object for which critical :class:`EngineerStep` attributes should be collected
  
      Returns
      -------
      attr_vals: Dict
          Critical :class:`EngineerStep` attributes. If `step_obj` does not have a necessary
          attribute (for `EngineerStep`) or a necessary key (for dict), its value in `attr_vals`
          will be a placeholder object. This is to facilitate comparison, while also ensuring
          missing values will always be considered unequal to other values
  
      Examples
      --------
      >>> def dummy_f(train_inputs, non_train_inputs):
      ...     return train_inputs, non_train_inputs
      >>> es_0 = EngineerStep(dummy_f)
      >>> EngineerStep.get_comparison_attrs(es_0)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
      {'name': 'dummy_f',
       'f': <function dummy_f at ...>,
       'params': ('train_inputs', 'non_train_inputs'),
       'stage': 'intra_cv',
       'do_validate': False}
      >>> EngineerStep.get_comparison_attrs(
      ...     dict(foo="hello", f=dummy_f, params=["all_inputs", "all_targets"], stage="pre_cv")
      ... )  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
      {'name': <object object at ...>,
       'f': <function dummy_f at ...>,
       'params': ('all_inputs', 'all_targets'),
       'stage': 'pre_cv',
       'do_validate': <object object at ...>}
      """
      # Attributes necessary for equality
      attr_names = ("name", "f", "params", "stage", "do_validate")
      if isinstance(step_obj, dict):
          attr_vals = {_: step_obj.get(_, object()) for _ in attr_names}
      else:
          attr_vals = {
              _: getattr(step_obj, _, object()) for _ in attr_names
          }
  
      # Ensure :attr:`params` is always a tuple, not a list
      attr_vals["params"] = tuple(attr_vals["params"])
      return attr_vals
  
imports: from hyperparameter_hunter.keys.hashing import make_hash_sha256
  from hyperparameter_hunter.space.dimensions import Categorical, RejectedOptional
  from hyperparameter_hunter.utils.boltons_utils import remap, default_visit, default_enter
  from hyperparameter_hunter.utils.general_utils import subdict
  import ast
  from contextlib import suppress
  from inspect import getsource
  import numpy
  import pandas
  from typing import List, Callable, Dict, Union, Tuple
label_desc: Use image node_id 13 for calling the EngineerStep obj's underlying member method code with example usage: obj.get_comparison_attrs(step_obj) and returns: attr_vals