docs: def nx_gen_node_attrs(G, key, nodes=None, default=util_const.NoParam,
                        on_missing='error', on_keyerr='default'):
      """
      Improved generator version of nx.get_node_attributes
  
      Args:
          on_missing (str): Strategy for handling nodes missing from G.
              Can be {'error', 'default', 'filter'}.  defaults to 'error'.
          on_keyerr (str): Strategy for handling keys missing from node dicts.
              Can be {'error', 'default', 'filter'}.  defaults to 'default'
              if default is specified, otherwise defaults to 'error'.
  
      Notes:
          strategies are:
              error - raises an error if key or node does not exist
              default - returns node, but uses value specified by default
              filter - skips the node
  
      Example:
          >>> # ENABLE_DOCTEST
          >>> from utool.util_graph import *  # NOQA
          >>> import utool as ut
          >>> G = nx.Graph([(1, 2), (2, 3)])
          >>> nx.set_node_attributes(G, name='part', values={1: 'bar', 3: 'baz'})
          >>> nodes = [1, 2, 3, 4]
          >>> #
          >>> assert len(list(ut.nx_gen_node_attrs(G, 'part', default=None, on_missing='error', on_keyerr='default'))) == 3
          >>> assert len(list(ut.nx_gen_node_attrs(G, 'part', default=None, on_missing='error', on_keyerr='filter'))) == 2
          >>> ut.assert_raises(KeyError, list, ut.nx_gen_node_attrs(G, 'part', on_missing='error', on_keyerr='error'))
          >>> #
          >>> assert len(list(ut.nx_gen_node_attrs(G, 'part', nodes, default=None, on_missing='filter', on_keyerr='default'))) == 3
          >>> assert len(list(ut.nx_gen_node_attrs(G, 'part', nodes, default=None, on_missing='filter', on_keyerr='filter'))) == 2
          >>> ut.assert_raises(KeyError, list, ut.nx_gen_node_attrs(G, 'part', nodes, on_missing='filter', on_keyerr='error'))
          >>> #
          >>> assert len(list(ut.nx_gen_node_attrs(G, 'part', nodes, default=None, on_missing='default', on_keyerr='default'))) == 4
          >>> assert len(list(ut.nx_gen_node_attrs(G, 'part', nodes, default=None, on_missing='default', on_keyerr='filter'))) == 2
          >>> ut.assert_raises(KeyError, list, ut.nx_gen_node_attrs(G, 'part', nodes, on_missing='default', on_keyerr='error'))
  
      Example:
          >>> # DISABLE_DOCTEST
          >>> # ALL CASES
          >>> from utool.util_graph import *  # NOQA
          >>> import utool as ut
          >>> G = nx.Graph([(1, 2), (2, 3)])
          >>> nx.set_node_attributes(G, name='full', values={1: 'A', 2: 'B', 3: 'C'})
          >>> nx.set_node_attributes(G, name='part', values={1: 'bar', 3: 'baz'})
          >>> nodes = [1, 2, 3, 4]
          >>> attrs = dict(ut.nx_gen_node_attrs(G, 'full'))
          >>> input_grid = {
          >>>     'nodes': [None, (1, 2, 3, 4)],
          >>>     'key': ['part', 'full'],
          >>>     'default': [util_const.NoParam, None],
          >>> }
          >>> inputs = ut.all_dict_combinations(input_grid)
          >>> kw_grid = {
          >>>     'on_missing': ['error', 'default', 'filter'],
          >>>     'on_keyerr': ['error', 'default', 'filter'],
          >>> }
          >>> kws = ut.all_dict_combinations(kw_grid)
          >>> for in_ in inputs:
          >>>     for kw in kws:
          >>>         kw2 = ut.dict_union(kw, in_)
          >>>         #print(kw2)
          >>>         on_missing = kw['on_missing']
          >>>         on_keyerr = kw['on_keyerr']
          >>>         if on_keyerr == 'default' and in_['default'] is util_const.NoParam:
          >>>             on_keyerr = 'error'
          >>>         will_miss = False
          >>>         will_keyerr = False
          >>>         if on_missing == 'error':
          >>>             if in_['key'] == 'part' and in_['nodes'] is not None:
          >>>                 will_miss = True
          >>>             if in_['key'] == 'full' and in_['nodes'] is not None:
          >>>                 will_miss = True
          >>>         if on_keyerr == 'error':
          >>>             if in_['key'] == 'part':
          >>>                 will_keyerr = True
          >>>             if on_missing == 'default':
          >>>                 if in_['key'] == 'full' and in_['nodes'] is not None:
          >>>                     will_keyerr = True
          >>>         want_error = will_miss or will_keyerr
          >>>         gen = ut.nx_gen_node_attrs(G, **kw2)
          >>>         try:
          >>>             attrs = list(gen)
          >>>         except KeyError:
          >>>             if not want_error:
          >>>                 raise AssertionError('should not have errored')
          >>>         else:
          >>>             if want_error:
          >>>                 raise AssertionError('should have errored')
  
      """
      if on_missing is None:
          on_missing = 'error'
      if default is util_const.NoParam and on_keyerr == 'default':
          on_keyerr = 'error'
      if nodes is None:
          nodes = G.nodes()
      # Generate `node_data` nodes and data dictionary
      node_dict = nx_node_dict(G)
      if on_missing == 'error':
          node_data = ((n, node_dict[n]) for n in nodes)
      elif on_missing == 'filter':
          node_data = ((n, node_dict[n]) for n in nodes if n in G)
      elif on_missing == 'default':
          node_data = ((n, node_dict.get(n, {})) for n in nodes)
      else:
          raise KeyError('on_missing={} must be error, filter or default'.format(
              on_missing))
      # Get `node_attrs` desired value out of dictionary
      if on_keyerr == 'error':
          node_attrs = ((n, d[key]) for n, d in node_data)
      elif on_keyerr == 'filter':
          node_attrs = ((n, d[key]) for n, d in node_data if key in d)
      elif on_keyerr == 'default':
          node_attrs = ((n, d.get(key, default)) for n, d in node_data)
      else:
          raise KeyError('on_keyerr={} must be error filter or default'.format(on_keyerr))
      return node_attrs