nodes: 0, module; 1, function_definition; 2, function_name:lazy_map; 3, parameters; 4, block; 5, identifier:data_processor; 6, identifier:data_generator; 7, default_parameter; 8, default_parameter; 9, expression_statement; 10, if_statement; 11, if_statement; 12, expression_statement; 13, with_statement; 14, return_statement; 15, identifier:n_cpus; 16, integer:1; 17, identifier:stepsize; 18, None; 19, comment:"""A variant of multiprocessing.Pool.map that supports lazy evaluation
  
      As with the regular multiprocessing.Pool.map, the processes are spawned off
      asynchronously while the results are returned in order. In contrast to
      multiprocessing.Pool.map, the iterator (here: data_generator) is not
      consumed at once but evaluated lazily which is useful if the iterator
      (for example, a generator) contains objects with a large memory footprint.
  
      Parameters
      ==========
      data_processor : func
          A processing function that is applied to objects in `data_generator`
  
      data_generator : iterator or generator
          A python iterator or generator that yields objects to be fed into the
          `data_processor` function for processing.
  
      n_cpus=1 : int (default: 1)
          Number of processes to run in parallel.
          - If `n_cpus` > 0, the specified number of CPUs will be used.
          - If `n_cpus=0`, all available CPUs will be used.
          - If `n_cpus` < 0, all available CPUs - `n_cpus` will be used.
  
      stepsize : int or None (default: None)
          The number of items to fetch from the iterator to pass on to the
          workers at a time.
          If `stepsize=None` (default), the stepsize size will
          be set equal to `n_cpus`.
  
      Returns
      =========
      list : A Python list containing the results returned
          by the `data_processor` function when called on
          all elements in yielded by the `data_generator` in
          sorted order. Note that the last list may contain
          fewer items if the number of elements in `data_generator`
          is not evenly divisible by `stepsize`.
      """; 20, not_operator; 21, block; 22, elif_clause; 23, comparison_operator:stepsize is None; 24, block; 25, assignment; 26, with_clause; 27, block; 28, identifier:results; 29, identifier:n_cpus; 30, expression_statement; 31, comparison_operator:n_cpus < 0; 32, block; 33, identifier:stepsize; 34, None; 35, expression_statement; 36, identifier:results; 37, list; 38, with_item; 39, while_statement; 40, assignment; 41, identifier:n_cpus; 42, integer:0; 43, expression_statement; 44, assignment; 45, as_pattern; 46, True; 47, block; 48, identifier:n_cpus; 49, call; 50, assignment; 51, identifier:stepsize; 52, identifier:n_cpus; 53, call; 54, as_pattern_target; 55, expression_statement; 56, if_statement; 57, attribute; 58, argument_list; 59, identifier:n_cpus; 60, binary_operator:mp.cpu_count() - n_cpus; 61, attribute; 62, argument_list; 63, identifier:p; 64, assignment; 65, identifier:r; 66, block; 67, else_clause; 68, identifier:mp; 69, identifier:cpu_count; 70, call; 71, identifier:n_cpus; 72, identifier:mp; 73, identifier:Pool; 74, keyword_argument; 75, identifier:r; 76, call; 77, expression_statement; 78, block; 79, attribute; 80, argument_list; 81, identifier:processes; 82, identifier:n_cpus; 83, attribute; 84, argument_list; 85, call; 86, break_statement; 87, identifier:mp; 88, identifier:cpu_count; 89, identifier:p; 90, identifier:map; 91, identifier:data_processor; 92, call; 93, attribute; 94, argument_list; 95, identifier:islice; 96, argument_list; 97, identifier:results; 98, identifier:extend; 99, identifier:r; 100, identifier:data_generator; 101, identifier:stepsize
code: def lazy_map(data_processor, data_generator, n_cpus=1, stepsize=None):
      """A variant of multiprocessing.Pool.map that supports lazy evaluation
  
      As with the regular multiprocessing.Pool.map, the processes are spawned off
      asynchronously while the results are returned in order. In contrast to
      multiprocessing.Pool.map, the iterator (here: data_generator) is not
      consumed at once but evaluated lazily which is useful if the iterator
      (for example, a generator) contains objects with a large memory footprint.
  
      Parameters
      ==========
      data_processor : func
          A processing function that is applied to objects in `data_generator`
  
      data_generator : iterator or generator
          A python iterator or generator that yields objects to be fed into the
          `data_processor` function for processing.
  
      n_cpus=1 : int (default: 1)
          Number of processes to run in parallel.
          - If `n_cpus` > 0, the specified number of CPUs will be used.
          - If `n_cpus=0`, all available CPUs will be used.
          - If `n_cpus` < 0, all available CPUs - `n_cpus` will be used.
  
      stepsize : int or None (default: None)
          The number of items to fetch from the iterator to pass on to the
          workers at a time.
          If `stepsize=None` (default), the stepsize size will
          be set equal to `n_cpus`.
  
      Returns
      =========
      list : A Python list containing the results returned
          by the `data_processor` function when called on
          all elements in yielded by the `data_generator` in
          sorted order. Note that the last list may contain
          fewer items if the number of elements in `data_generator`
          is not evenly divisible by `stepsize`.
      """
      if not n_cpus:
          n_cpus = mp.cpu_count()
      elif n_cpus < 0:
          n_cpus = mp.cpu_count() - n_cpus
  
      if stepsize is None:
          stepsize = n_cpus
  
      results = []
  
      with mp.Pool(processes=n_cpus) as p:
          while True:
              r = p.map(data_processor, islice(data_generator, stepsize))
              if r:
                  results.extend(r)
              else:
                  break
      return results