docs: def get_sub_dsp(self, nodes_bunch, edges_bunch=None):
          """
          Returns the sub-dispatcher induced by given node and edge bunches.
  
          The induced sub-dispatcher contains the available nodes in nodes_bunch
          and edges between those nodes, excluding those that are in edges_bunch.
  
          The available nodes are non isolated nodes and function nodes that have
          all inputs and at least one output.
  
          :param nodes_bunch:
              A container of node ids which will be iterated through once.
          :type nodes_bunch: list[str], iterable
  
          :param edges_bunch:
              A container of edge ids that will be removed.
          :type edges_bunch: list[(str, str)], iterable, optional
  
          :return:
              A dispatcher.
          :rtype: Dispatcher
  
          .. seealso:: :func:`get_sub_dsp_from_workflow`
  
          .. note::
  
              The sub-dispatcher edge or node attributes just point to the
              original dispatcher. So changes to the node or edge structure
              will not be reflected in the original dispatcher map while changes
              to the attributes will.
  
          **--------------------------------------------------------------------**
  
          **Example**:
  
          A dispatcher with a two functions `fun1` and `fun2`:
  
          .. dispatcher:: dsp
             :opt: graph_attr={'ratio': '1'}
  
              >>> dsp = Dispatcher(name='Dispatcher')
              >>> dsp.add_function(function_id='fun1', inputs=['a', 'b'],
              ...                   outputs=['c', 'd'])
              'fun1'
              >>> dsp.add_function(function_id='fun2', inputs=['a', 'd'],
              ...                   outputs=['c', 'e'])
              'fun2'
  
          Get the sub-dispatcher induced by given nodes bunch::
  
              >>> sub_dsp = dsp.get_sub_dsp(['a', 'c', 'd', 'e', 'fun2'])
  
          .. dispatcher:: sub_dsp
             :opt: graph_attr={'ratio': '1'}
  
              >>> sub_dsp.name = 'Sub-Dispatcher'
          """
  
          # Get real paths.
          nodes_bunch = [self.get_node(u)[1][0] for u in nodes_bunch]
  
          # Define an empty dispatcher.
          sub_dsp = self.copy_structure(
              dmap=self.dmap.subgraph(nodes_bunch).copy()
          )
  
          # Namespace shortcuts for speed.
          nodes, dmap_out_degree = sub_dsp.nodes, sub_dsp.dmap.out_degree
          dmap_dv, dmap_rm_edge = self.default_values, sub_dsp.dmap.remove_edge
          dmap_rm_node = sub_dsp.dmap.remove_node
  
          # Remove function nodes that has not whole inputs available.
          for u in nodes_bunch:
              n = nodes[u].get('inputs', None)  # Function inputs.
              # No all inputs
              if n is not None and not set(n).issubset(nodes_bunch):
                  dmap_rm_node(u)  # Remove function node.
  
          # Remove edges that are not in edges_bunch.
          if edges_bunch is not None:
              for e in edges_bunch:  # Iterate sub-graph edges.
                  dmap_rm_edge(*e)  # Remove edge.
  
          # Remove function node with no outputs.
          for u in [u for u, n in sub_dsp.dmap.nodes.items()
                    if n['type'] == 'function']:
  
              # noinspection PyCallingNonCallable
              if not dmap_out_degree(u):  # No outputs.
                  dmap_rm_node(u)  # Remove function node.
  
          from networkx import isolates
          # Remove isolate nodes from sub-graph.
          sub_dsp.dmap.remove_nodes_from(list(isolates(sub_dsp.dmap)))
  
          # Set default values.
          sub_dsp.default_values = {k: dmap_dv[k] for k in dmap_dv if k in nodes}
  
          return sub_dsp